Strange behaviour with sys.save() (SOLVED)

Hi,

I’m having a strange issue with sys.save or better with shared lua states.
I have my loading/saving logic in the same script that I use to load/unload collection proxies and I was planning to trigger a save by sending a message.

What I’m seeing is that the script receives the message fine and triggers the sys.save part, but the content of the table I’m trying to save is not updated. It’s like the shared state of the lua script containing the data is not updated.
I feel like I’m missing something or doing something not correct.

I’ve attached the project for testing.

source.zip (862.2 KB)

Forgot to mention that this happens on both stable and latest beta release.

I’m not sure what’s going to happen (remember to provide clear Steps to Reproduce, Expected Outcome and Actual Outcome!).

First start (splash+click):

DEBUG:SCRIPT: /Users/bjornritzl/Library/Application Support/woords/gamedata
DEBUG:SCRIPT: Running on: Desktop
DEBUG:SCRIPT: Resolution: 640 x 1136 @ 0.65
DEBUG:SCRIPT: Debug Mode: True
DEBUG:SCRIPT: Safe Area: False
DEBUG:SCRIPT: Network: Online
DEBUG:SCRIPT: Gamedata: Created
DEBUG:SCRIPT: 
{ } --[[0x112ff0140]]
INFO:DLIB: SSDP: Started on address 192.168.0.126
DEBUG:SCRIPT: about to send msg
DEBUG:SCRIPT: 
{ --[[0x112ff0140]]
  test = "TEST TEST TEST"
}
DEBUG:SCRIPT: save data message received!
DEBUG:SCRIPT: 
{ --[[0x112ff0140]]
  test = "TEST TEST TEST"
}

Ok, looks like it saved something. I close down the game and start again:

DEBUG:SCRIPT: /Users/bjornritzl/Library/Application Support/woords/gamedata
DEBUG:SCRIPT: Running on: Desktop
DEBUG:SCRIPT: Resolution: 640 x 1136 @ 0.65
DEBUG:SCRIPT: Debug Mode: True
DEBUG:SCRIPT: Safe Area: False
DEBUG:SCRIPT: Network: Online
DEBUG:SCRIPT: Gamedata: Loaded
DEBUG:SCRIPT: 
{ --[[0x110f92a90]]
  test = "TEST TEST TEST"
}

Ah yes dummy me you didn’t have the original save file.

If you change the values of gamedata.test in simple_clip.script, you will see that the new value is not getting saved after the msg is posted.

This looks strange though (removed some stuff):

local gamedata = require("main.gamedata")

local function bootstrap_gamedata()
	print(path)
	local exists = sys.load(path)
	if not next(exists) then
		sys.save(path, gamedata)		
	else
		gamedata = sys.load(path)
		gamedata_loaded = true
		--gamedata = {}	
	end
	exists = nil
end

What do you expect should happen here?

		gamedata = sys.load(path)

If you expect that the “main.gamedata” should update then no, it will remain the same. You are replacing the content of the local gamedata variable. Not the actual content of the table returned in “main.gamedata”.

If gamedata.lua would look like this:

return {
  data = {}
}

And you do:

sys.save(path, gamedata.data)
...
gamedata.data = sys.load(path)

Then it’s a different story.

1 Like

Being gamedata a shared table (I load it in main and in the click script), shouldn’t that update properly? this is at least what I normally do in other frameworks.

mmmm…let me get a different example with less things going on. I think saving loading data is fine, the issue I have is somewhere else.

Yes, please share a minimal example. But as I wrote in my reply:

gamedata = sys.load(path)

The above is very likely not doing what you expect it to do

Yes, I’m narrowing it down.
The issue starts if a save game is already there and I load it with sys.load.
I was expecting the content of gamedata to be replaced once you load with sys.load. is this not the case?

I’ve made a simpler case. Left mouse click to move between screens. Right mouse click on screenB will change the values of the shared lua file.
On first run, when the save file is generated everything seems to be working fine.
If you run a second time (save file already there), then it becomes strange.
The first print is correct (the saved file), but the screenA print is wrong (?) and while screenB print is correct, if you try and alter the value by pressing the RMB this doesn’t work(!).

shared_state.zip (631.3 KB)

I’ve tried your suggestion and used a table in a table for the shared lua file and that works perfect.

Is there a specific (internal workings) for this? As I said in my previous post, I was expecting the content of gamedata to be replaced once you load with sys.load.

It would be great to mention it in the docs because it seems like you should be able to do otherwise.

No no, this is not something Defold specific. It is how Lua works. I’ll try to break it down and explain what happens:

When you call require(“foobar”) Lua will first check if “foobar” has already been loaded. It does so by checking the package.loaded table. The package.loaded table contains the results of loaded modules, keyed on the require path. Example:

foobar.lua

return {
    foo = "bar"
}

a.script

print(package.loaded["foobar"]) -- nil
local t = require("foobar")
print(t.foo) -- "bar"
print(package.loaded["foobar"]) -- table: 0x01135e4e90
print(package.loaded["foobar"].foo) -- "bar"
print(t == package.loaded["foobar"]) -- true

The next time you require("foobar") you will get back the same table from package.loaded.

Now that we know how require works we can start looking at your example. Which basically is this:

a.script

local foobar = require("foobar")
print(foobar) --  table: 0x01135e4e90
print(foobar.foo) -- "bar"

function init(self)
    foobar = sys.load("some/file") -- this file contains a table with key "foo" = "boo"
    print(foobar) --  table: 0x01135e8e90 <-- another table than above
    print(foobar.foo) -- "boo"
end

b.script

local foobar = require("foobar")
print(foobar) --  table: 0x01135e4e90
print(foobar.foo) -- "bar"

function update(self, dt)
    print(foobar.foo) -- "bar"
end

If I understand you correctly you expect that b.script will print “boo” since you replace the foobar table with the content of the file “some/file”. But this is not the case!

local foobar = require("foobar")
print(foobar) --  table: 0x01135e4e90
print(package.loaded["foobar"])  --  table: 0x01135e4e90
foobar = sys.load("some/file")
print(foobar) --  table: 0x01135e8e90 <-- another table
print(package.loaded["foobar"])  --  table: 0x01135e4e90
  • What we have done above is to require(“foobar”) and assign the returned tabled to a local variable “foobar”.
  • The table assigned to the local variable “foobar” is the same table that we have stored in package.loaded
  • Now when we load a new table using sys.load() we assign it to the local variable “foobar”. We do NOT change what we have in package.loaded.
  • This means that any other script with a reference to the table in package.loaded will not have the content of the loaded table from “some/file”.
  • This is why I recommend that you replace a value INSIDE the table, since that means that all scripts will have a reference to the same table and access to the same content in the table.
2 Likes

Right, now it’s clear. Thanks a lot for the explanation!