Jump to content

Save an instance in an component not possible!?


Recommended Posts

Why is it not possible to save an instance in an component?

I made a component that is meant to save and load some imponrtant stuff and is assigned to the world:

Spoiler

local RememberStuff = Class(function(self, inst)
    self.inst = inst
    self.stuff1 = {}
    self.stuff2 = {}
    self.stuff3 = {}
    self.stuff4 = {}
    self.stuff5 = {}
    self.stuff6 = {}
    self.stuff7 = {}
    self.stuff8 = {}
    self.stuff9 = {}
    self.stuff10 = {}
end)


function RememberStuff:OnSave()
	return {
    stuff1 = self.stuff1,
    stuff2 = self.stuff2,
    stuff3 = self.stuff3,
    stuff4 = self.stuff4,
    stuff5 = self.stuff5,
    stuff6 = self.stuff6,
    stuff7 = self.stuff7,
    stuff8 = self.stuff8,
    stuff9 = self.stuff9,
    stuff10 = self.stuff10,
    }
end

function RememberStuff:OnLoad(data)
    self.stuff1 = data and data.stuff1 or {}
    self.stuff2 = data and data.stuff2 or {}
    self.stuff3 = data and data.stuff3 or {}
    self.stuff4 = data and data.stuff4 or {}
    self.stuff5 = data and data.stuff5 or {}
    self.stuff6 = data and data.stuff6 or {}
    self.stuff7 = data and data.stuff7 or {}
    self.stuff8 = data and data.stuff8 or {}
    self.stuff9 = data and data.stuff9 or {}
    self.stuff10 = data and data.stuff10 or {}
end


return RememberStuff

 

But as soon as I save any instance in one of the stuff variables, the game will crash as soon as the game is saved (after one day or when leaving the game) with the error:
[string "scripts/dumper.lua"]:112: Cannot dump userdata (Pathfinder (1562C950) - unknown)
In this case I just made directly in the component function: self.stuff2 = inst
 

Spoiler

[00:00:34]: [string "scripts/dumper.lua"]:112: Cannot dump userdata (Pathfinder (1562C950) - unknown)
LUA ERROR stack traceback:
=[C]:-1 in (global) error (C) <-1--1>
scripts/dumper.lua:112 in () ? (Lua) <98-113>
   value = Pathfinder (1562C950)
   var = nil
   i = 8
=(tail call):-1 in ()  (tail) <-1--1>
scripts/dumper.lua:151 in () ? (Lua) <141-159>
   value = 100034 - world (valid:true)
   numidx = 1
   key = Pathfinder
   val = Pathfinder (1562C950)
=(tail call):-1 in ()  (tail) <-1--1>
scripts/dumper.lua:151 in () ? (Lua) <141-159>
   value = table: 3D9DE9E0
   numidx = 1
   key = stuff2
   val = 100034 - world (valid:true)
=(tail call):-1 in ()  (tail) <-1--1>
scripts/dumper.lua:151 in () ? (Lua) <141-159>
   value = table: 3D9DE1E8
   numidx = 1
   key = rememberstuff
   val = table: 3D9DE9E0
=(tail call):-1 in ()  (tail) <-1--1>
scripts/dumper.lua:151 in () ? (Lua) <141-159>
   value = table: 3D9DD040
   numidx = 1
   key = persistdata
   val = table: 3D9DE1E8
=(tail call):-1 in ()  (tail) <-1--1>
scripts/dumper.lua:151 in () ? (Lua) <141-159>
   value = table: 3D38D460
   numidx = 1
   key = map
   val = table: 3D9DD040
=(tail call):-1 in ()  (tail) <-1--1>
scripts/dumper.lua:225 in (global) DataDumper (Lua) <77-245>
   value = table: 3D38D460
   varname = return
   fastmode = true
   ident = nil
   defined = table: 3D9DF5E8
   dumplua = function - scripts/dumper.lua:214
   string_format = function - =[C]:-1
   type = function - =[C]:-1
   string_dump = function - =[C]:-1
   string_rep = function - =[C]:-1
   tostring = function - =[C]:-1
   pairs = function - =[C]:-1
   table_concat = function - =[C]:-1
   keycache = table: 3D9DF548
   strvalcache = table: 3D9DF570
   out = table: 3D9DF610
   closure_cnt = 0
   fcts = table: 3D9DFBD8
   test_defined = function - scripts/dumper.lua:116
   make_key = function - scripts/dumper.lua:127
scripts/mainfunctions.lua:653 in (global) SaveGame (Lua) <535-664>
   isshutdown = nil
   cb = nil
   save = table: 3D38D460
   nument = 3027
   saved_ents = table: 3D38D4D8
   references = table: 3D38D500
   new_refs = nil
   ground = 100034 - world (valid:true)
   PRETTY_PRINT = false
scripts/saveindex.lua:339 in (method) SaveCurrent (Lua) <328-340>
   self =
      data = table: 1664F178
      current_slot = 3
   onsavedcb = nil
   isshutdown = nil
   slotdata = table: 1664F330
scripts/components/autosaver.lua:85 in (field) fn (Lua) <59-87>
   inst = 100037 - forest_network (valid:true)
   taskid = 1
   snapshot = nil
scripts/scheduler.lua:177 in (method) OnTick (Lua) <155-207>
   self =
      running = table: 33E9FEC0
      waitingfortick = table: 33EA00F0
      tasks = table: 33EA0258
      waking = table: 3D38D0C8
      attime = table: 33E9FF10
      hibernating = table: 33EA0028
   tick = 116
   k = PERIODIC 100037: 1.000000
   v = true
   already_dead = false
scripts/scheduler.lua:371 in (global) RunScheduler (Lua) <369-377>
   tick = 116
scripts/update.lua:166 in () ? (Lua) <150-223>
   dt = 0.033333335071802
   tick = 116
   i = 116

[00:00:34]: [string "scripts/dumper.lua"]:112: Cannot dump userdata (Pathfinder (1562C950) - unknown)
LUA ERROR stack traceback:
    =[C]:-1 in (global) error (C) <-1--1>
    scripts/dumper.lua:112 in () ? (Lua) <98-113>
    =(tail call):-1 in ()  (tail) <-1--1>
    scripts/dumper.lua:151 in () ? (Lua) <141-159>
    =(tail call):-1 in ()  (tail) <-1--1>
    scripts/dumper.lua:151 in () ? (Lua) <141-159>
    =(tail call):-1 in ()  (tail) <-1--1>
    scripts/dumper.lua:151 in () ? (Lua) <141-159>
    =(tail call):-1 in ()  (tail) <-1--1>
    scripts/dumper.lua:151 in () ? (Lua) <141-159>
    =(tail call):-1 in ()  (tail) <-1--1>
    scripts/dumper.lua:151 in () ? (Lua) <141-159>
    =(tail call):-1 in ()  (tail) <-1--1>
    scripts/dumper.lua:225 in (global) DataDumper (Lua) <77-245>
    scripts/mainfunctions.lua:653 in (global) SaveGame (Lua) <535-664>
    scripts/saveindex.lua:339 in (method) SaveCurrent (Lua) <328-340>
    scripts/components/autosaver.lua:85 in (field) fn (Lua) <59-87>
    scripts/scheduler.lua:177 in (method) OnTick (Lua) <155-207>
    scripts/scheduler.lua:371 in (global) RunScheduler (Lua) <369-377>
    scripts/update.lua:166 in () ? (Lua) <150-223>

I already had such error messages when I tried to use this "dump" command myself for arrays. So I just made my own dump-like function.
But in this case I can't use my own dump function...

So what to do now? How can I save instances in an component?
Or do I have to report this as a bug, cause the dump function is stupid ?

Edited by Serpens
Link to comment
Share on other sites

You almost never save entities. Game does it if they persist.

 

You either do it like components do (example, herd):

function Herd:OnSave()
    local data = {}

    for k, v in pairs(self.members) do
        if data.members == nil then
            data.members = { k.GUID }
        else
            table.insert(data.members, k.GUID)
        end
    end

    return data, data.members
end

function Herd:LoadPostPass(newents, savedata)
    if savedata.members ~= nil then
        for k, v in pairs(savedata.members) do
            local member = newents[v]
            if member ~= nil then
                self:AddMember(member.entity)
            end
        end
    end
end

They save the GUIDs.

LoadPostPass runs after the entities and the world are loaded, and the ids assigned.

newents holds all the world's loaded entities, so the function gets the entity by using the saved GUID as key.

 

Or like how Wendy does it:

local function OnSave(inst, data)
    if inst.abigail ~= nil then
        data.abigail = inst.abigail:GetSaveRecord()
    end
end

local function OnLoad(inst, data)
    if data.abigail ~= nil and inst.abigail == nil then
        local abigail = SpawnSaveRecord(data.abigail)
        if abigail ~= nil then
            if inst.migrationpets ~= nil then
                table.insert(inst.migrationpets, abigail)
            end
            abigail.SoundEmitter:PlaySound("dontstarve/common/ghost_spawn")
            abigail:LinkToPlayer(inst)
        end
    end
end

With GetSaveRecord and SpawnSaveRecord.

 

But really, keep it simple.

WHY are you saving THE WORLD (the Pathfinder component)?

Absurd. DO NOT save the world yourself.

Put the particular data you want to save in a table, and save it, then retrieve it when loading.

Link to comment
Share on other sites

The test case to which the log belongs is simply stuff2=inst:

Spoiler

local RememberStuff = Class(function(self, inst)
    self.inst = inst
    self.stuff1 = {}
    self.stuff2 = inst
    self.stuff3 = {}
    self.stuff4 = {}
    self.stuff5 = {}
    self.stuff6 = {}
    self.stuff7 = {}
    self.stuff8 = {}
    self.stuff9 = {}
    self.stuff10 = {}
end)


function RememberStuff:OnSave()
	return {
    stuff1 = self.stuff1,
    stuff2 = self.stuff2,
    stuff3 = self.stuff3,
    stuff4 = self.stuff4,
    stuff5 = self.stuff5,
    stuff6 = self.stuff6,
    stuff7 = self.stuff7,
    stuff8 = self.stuff8,
    stuff9 = self.stuff9,
    stuff10 = self.stuff10,
    }
end

function RememberStuff:OnLoad(data)
    self.stuff1 = data and data.stuff1 or {}
    self.stuff2 = data and data.stuff2 or {}
    self.stuff3 = data and data.stuff3 or {}
    self.stuff4 = data and data.stuff4 or {}
    self.stuff5 = data and data.stuff5 or {}
    self.stuff6 = data and data.stuff6 or {}
    self.stuff7 = data and data.stuff7 or {}
    self.stuff8 = data and data.stuff8 or {}
    self.stuff9 = data and data.stuff9 or {}
    self.stuff10 = data and data.stuff10 or {}
end


return RememberStuff

This is just a test case everyone can easily replicate and where I can be sure, that I made no other mistakes.

The main goal is to place at first game start a firepit somewhere and everytime a new player spawns, this firepit gets some fuel. So I have to save this firepit somewhere.
I will try this GUID thing, thank you :)

Edited by Serpens
Link to comment
Share on other sites

On 7.10.2016 at 8:00 PM, DarkXero said:

You either do it like components do (example, herd):


function Herd:OnSave()
    local data = {}

    for k, v in pairs(self.members) do
        if data.members == nil then
            data.members = { k.GUID }
        else
            table.insert(data.members, k.GUID)
        end
    end

    return data, data.members
end

function Herd:LoadPostPass(newents, savedata)
    if savedata.members ~= nil then
        for k, v in pairs(savedata.members) do
            local member = newents[v]
            if member ~= nil then
                self:AddMember(member.entity)
            end
        end
    end
end

They save the GUIDs.

LoadPostPass runs after the entities and the world are loaded, and the ids assigned.

newents holds all the world's loaded entities, so the function gets the entity by using the saved GUID as key.

This is now my rememberstuff component:
 

local RememberStuff = Class(function(self, inst)
    self.inst = inst
    self.stuff1 = {}
    self.stuff2 = {}
    self.stuff3 = {}
    self.stuff4 = {}
    self.stuff5 = {}
    self.stuff6 = {}
    self.stuff7 = {}
    self.stuff8 = {}
    self.stuff9 = {}
    self.stuff10 = {}
end)


function RememberStuff:OnSave()
	local data = {}
    for i=1,10 do
        data["stuff"..i] = {}
        if type(self["stuff"..i])=="table" then
            for key,thing in pairs(self["stuff"..i]) do
                if type(thing)=="table" and thing.GUID then -- if it is an entity, only save the GUID!
                    table.insert(data["stuff"..i],thing.GUID)
                else
                    table.insert(data["stuff"..i],thing)
                end
            end
        end
    end
    return data
end

function RememberStuff:OnLoad(data)
    self.stuff1 = data and data.stuff1 or {}
    self.stuff2 = data and data.stuff2 or {}
    self.stuff3 = data and data.stuff3 or {}
    self.stuff4 = data and data.stuff4 or {}
    self.stuff5 = data and data.stuff5 or {}
    self.stuff6 = data and data.stuff6 or {}
    self.stuff7 = data and data.stuff7 or {}
    self.stuff8 = data and data.stuff8 or {}
    self.stuff9 = data and data.stuff9 or {}
    self.stuff10 = data and data.stuff10 or {}
end

function RememberStuff:LoadPostPass(newents, data)    
    for i=1,10 do -- now revert GUIDs back to entities
        if type(self["stuff"..i])=="table" then
            for key,thing in pairs(self["stuff"..i]) do -- data was already saved in self in the onload function
                if type(thing)=="number" and newents[thing]~=nil then -- if thing is GUID, this will be true
                    self["stuff"..i][key] = newents[thing].entity -- replace the GUID with the entity
                else
                    print("LoadPostPass: type: "..type(thing).." thing: "..tostring(thing).."ent: "..tostring(newents[thing]))
                end
            end
        end
    end
end

return RememberStuff

Everything works, except getting the old entity with helpf of the GUID.

The GUID is saved and loaded succesfully, but newents[thing] is always nil and I don't get why ... The lenght of newents is ~ 3000 so it seems tho be the correct table...

Edited by Serpens
Link to comment
Share on other sites

LoadPostPass doesn't "load the entities".

The game saves and loads the entities.

What LoadPostPass does is hook up the old GUIDs so they remain constant.

Which you are doing incorrectly because you are forgetting my "stuff_references" table.

local RememberStuff = Class(function(self, inst)
	self.inst = inst
	self.stuff1 = {}
	self.stuff2 = {}
	self.stuff3 = {}
	self.stuff4 = {}
	self.stuff5 = {}
	self.stuff6 = {}
	self.stuff7 = {}
	self.stuff8 = {}
	self.stuff9 = {}
	self.stuff10 = {}
end)


function RememberStuff:OnSave()
	local data = {}
	local stuff_references = {}

	for i = 1, 10 do
		local data_stuff = {}
		local self_stuff = self["stuff"..i]

		for key, thing in pairs(self_stuff) do
			if type(thing) == "table" and thing.GUID then
				-- we need to pass the GUID references to the game
				table.insert(stuff_references, thing.GUID)
				-- save a table with flag and GUID where entity should be
				table.insert(data_stuff, { is_GUID = true, GUID = thing.GUID })
			else
				table.insert(data_stuff, thing)
			end
		end

		data["stuff"..i] = data_stuff
	end

	if next(stuff_references) == nil then
		-- stuff_references is empty, make it nil
		stuff_references = nil
	end

	return data, stuff_references
end

function RememberStuff:OnLoad(data)
	self.stuff1 = data and data.stuff1 or {}
	self.stuff2 = data and data.stuff2 or {}
	self.stuff3 = data and data.stuff3 or {}
	self.stuff4 = data and data.stuff4 or {}
	self.stuff5 = data and data.stuff5 or {}
	self.stuff6 = data and data.stuff6 or {}
	self.stuff7 = data and data.stuff7 or {}
	self.stuff8 = data and data.stuff8 or {}
	self.stuff9 = data and data.stuff9 or {}
	self.stuff10 = data and data.stuff10 or {}
end

function RememberStuff:LoadPostPass(newents, data)
	-- OnLoad runs before LoadPostPass
	-- we saved GUIDs in data
	-- therefore, stuff1 loaded the tables with { is_GUID, GUID } where there should be an entity instead
	-- therefore, we just need to swap
	for i = 1, 10 do
		local self_stuff = self["stuff"..i]

		for key, thing in pairs(self_stuff) do
			if type(thing) == "table" then
				if thing.is_GUID then
					local thingy = newents[thing.GUID]
					if thingy then
						-- swap temp table for entity
						self_stuff[key] = thingy.entity
					end
				end
			end
		end
	end
end

return RememberStuff

 

If your entities don't get saved by the game because you put "inst.persists = false" in your prefab, you can consider:

local RememberStuff = Class(function(self, inst)
	self.inst = inst
	self.stuff1 = {}
	self.stuff2 = {}
	self.stuff3 = {}
	self.stuff4 = {}
	self.stuff5 = {}
	self.stuff6 = {}
	self.stuff7 = {}
	self.stuff8 = {}
	self.stuff9 = {}
	self.stuff10 = {}
end)


function RememberStuff:OnSave()
	local data = {}

	for i = 1, 10 do
		local data_stuff = {}
		local self_stuff = self["stuff"..i]

		for key, thing in pairs(self_stuff) do
			if type(thing) == "table" and thing.GUID then
				table.insert(data_stuff, { is_SaveRecord = true, SaveRecord = thing:GetSaveRecord() })
			else
				table.insert(data_stuff, thing)
			end
		end

		data["stuff"..i] = data_stuff
	end

	return data
end

function RememberStuff:OnLoad(data)
	self.stuff1 = data and data.stuff1 or {}
	self.stuff2 = data and data.stuff2 or {}
	self.stuff3 = data and data.stuff3 or {}
	self.stuff4 = data and data.stuff4 or {}
	self.stuff5 = data and data.stuff5 or {}
	self.stuff6 = data and data.stuff6 or {}
	self.stuff7 = data and data.stuff7 or {}
	self.stuff8 = data and data.stuff8 or {}
	self.stuff9 = data and data.stuff9 or {}
	self.stuff10 = data and data.stuff10 or {}
end

function RememberStuff:LoadPostPass(newents, data)
	for i = 1, 10 do
		local self_stuff = self["stuff"..i]

		for key, thing in pairs(self_stuff) do
			if type(thing) == "table" then
				if thing.is_SaveRecord then
					local thingy = SpawnSaveRecord(thing.SaveRecord, newents)
					if thingy then
						self_stuff[key] = thingy
					end
				end
			end
		end
	end
end

return RememberStuff

 

Link to comment
Share on other sites

Great it works, thanks :)
Since I want to keep the previous keys (named) and I forgot that in my previous code, I changed the part of the save function to use the key (does work, just if someone else is also interested):
 

		for key, thing in pairs(self_stuff) do
			if type(thing) == "table" and thing.GUID then
				-- we need to pass the GUID references to the game
				table.insert(stuff_references, thing.GUID)
				-- save a table with flag and GUID where entity should be
				data_stuff[key] = { is_GUID = true, GUID = thing.GUID }
			else
				data_stuff[key] = thing
			end
		end

 

Edited by Serpens
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
  • Create New...