[1.1.32]Reference counting in lua memory managing doesn't function properly.

Place to get help with not working mods / modding interface.
yagaodirac
Fast Inserter
Fast Inserter
Posts: 152
Joined: Sun Jun 16, 2019 4:04 pm
Contact:

[1.1.32]Reference counting in lua memory managing doesn't function properly.

Post by yagaodirac »

I also post this to bug report.
Also, I would ask, if this is correct according to the memory mechanism in lua. I remember the referencing mechanism is something called reference counting. If variable a stores a table, then b = a, after this assignment, a still store the pointer to the table.
But in this case, a nearly equals nil.
This trouble makes it even harder to write pseudo-OOP code.
Any idea? Thanks.

Code: Select all

global = global or {}
global.mt = global.mt or {}
global.mt.test_array = {}
global.mt.test_array.what = "test_array"


global.mt.test_array.add = function(this, element)
	this.length = this.length +1
	rawset(this, this.length, element)
end

-------------- CTOR n DTOR ----------------
global.new = global.new or {}
global.new.test_array = function()
	local this = {}
	this.length = 0
	this.mt = global.mt.test_array
	return this
end

---------------------------------------
global.cont = global.new.test_array()

script.on_event(defines.events.on_tick, function(event)
	if game.tick == 1 
	then
		global.cont.mt.add(global.cont, "11111111111")
	end
	if game.tick == 3 
	then
		log(serpent.block(global))----------  Calls the log function.
	end
end)
The global.mt.test_array should always store the add function inside it.
But the log is very clear.

Code: Select all

Factorio_1.1.32/temp/currently-playing/control.lua:54: {
  cont = {
    "11111111111",
    length = 1,
    mt = {
      add = ((loadstring or load)("\27LuaR\0\1\4\8\4\8\0\25揬13\n\26\n\19\0\0\0\22\0\0\0\2\0\6\9\0\0\0嘰0@\0岪@\1\n€\0€唨@\0繺0\0\0\7\1@\0@\1€\0滰\0\2\31\0€\0\3\0\0\0\4\7\0\0\0\0\0\0\0length\0\3\0\0\0\0\0\0?\4\7\0\0\0\0\0\0\0rawset\0\0\0\0\0\1\0\0\0\0\0L\0\0\0\0\0\0\0@D:/factorio stand alone/Factorio_1.1.32/temp/currently-playing/control.lua\0\9\0\0\0\20\0\0\0\20\0\0\0\20\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\21\0\0\0\22\0\0\0\2\0\0\0\5\0\0\0\0\0\0\0this\0\0\0\0\0\9\0\0\0\8\0\0\0\0\0\0\0element\0\0\0\0\0\9\0\0\0\1\0\0\0\5\0\0\0\0\0\0\0_ENV\0",'@serialized')),
      what = "test_array"
    }
  },
  mt = {----------------------this table shouldn't be removed.
    test_array = 0
  },
  new = {
    test_array = ((loadstring or load)("\27LuaR\0\1\4\8\4\8\0\25揬13\n\26\n\26\0\0\0\31\0\0\0\0\0\2\8\0\0\0\11\0\0\0\n@@€F繞\0G€繺0G\0羂0\n@\0乗31\0\0\1\31\0€\0\5\0\0\0\4\7\0\0\0\0\0\0\0length\0\3\0\0\0\0\0\0\0\0\4\3\0\0\0\0\0\0\0mt\0\4\7\0\0\0\0\0\0\0global\0\4\11\0\0\0\0\0\0\0test_array\0\0\0\0\0\1\0\0\0\0\0L\0\0\0\0\0\0\0@D:/factorio stand alone/Factorio_1.1.32/temp/currently-playing/control.lua\0\8\0\0\0\27\0\0\0\28\0\0\0\29\0\0\0\29\0\0\0\29\0\0\0\29\0\0\0\30\0\0\0\31\0\0\0\1\0\0\0\5\0\0\0\0\0\0\0this\0\1\0\0\0\8\0\0\0\1\0\0\0\5\0\0\0\0\0\0\0_ENV\0",'@serialized'))
  }
}
No idea what happened. The = operator doesn't function in the reference counting way. Is this correct in lua?

User avatar
boskid
Factorio Staff
Factorio Staff
Posts: 2739
Joined: Thu Dec 14, 2017 6:56 pm
Contact:

Re: [1.1.32]Reference counting in lua memory managing doesn't function properly.

Post by boskid »

Removed topic from bug report. Do not double-post.

Please read https://lua-api.factorio.com/latest/Data-Lifecycle.html - point 3. It looks like you are trying to assign to a `global` variable on the top level of a control.lua which is executed when the global is not yet available. You may need to add a `script.on_init` hook to setup initial values.

yagaodirac
Fast Inserter
Fast Inserter
Posts: 152
Joined: Sun Jun 16, 2019 4:04 pm
Contact:

Re: [1.1.32]Reference counting in lua memory managing doesn't function properly.

Post by yagaodirac »

boskid wrote:
Tue Apr 20, 2021 1:57 pm
Thanks.
The document is not helpful enough. It doesn't show what to do. You mean, on_init is something for the setup of global, and on_load is for the metatable? OK, I'll give it a try.

User avatar
boskid
Factorio Staff
Factorio Staff
Posts: 2739
Joined: Thu Dec 14, 2017 6:56 pm
Contact:

Re: [1.1.32]Reference counting in lua memory managing doesn't function properly.

Post by boskid »

I think most of the issues you are encountering are because the lua environment for mods in factorio is by no means standard.

Because of the save-load which may happen at any point in time (and when having /toggle-heavy-mode turned on, at every possible point in time), control.lua has to have some particular structure to work properly.

Lua context is not persisted across the save-load. Only data from `global` are persisted, and only some simple types (numbers, strings and others simple types) so lua functions and metatables will not survive the save-load.
When the mod lua context is reconstructed during load, lua context is basically empty with no way of factorio knowing where are all the hooks to on_init etc, so the control.lua is loaded into that fresh lua context so it loads all its required dependencies (other lua files) into its environment but most important: it registers hooks. When control.lua is executed on the top level, it is literally the step 3 "control.lua initialization". After this step which is intended only to setup lua environment, there are on_init (when mod is loaded for the first time) and on_load hooks called to finish with the environment setup so it is ready for main part of the game.

At certain stages of setup there are certain tables not available to avoid some unintended interactions (having `global` available in the control.lua initialization in theory is possible, but since it may contain lua objects it would allow to tamper with a game state when it is not yet ready).

Best hint for you right now: keep in mind that at any point in time your lua environment may simply get wiped entirely and only data persisted will be those in `global`. When that happens, your control.lua will get executed from top-to-bottom once more in a fresh lua environment and you should not have any logic at the top level that would change behavior of the remaining code (that includes non persisted variables set on top level)

yagaodirac
Fast Inserter
Fast Inserter
Posts: 152
Joined: Sun Jun 16, 2019 4:04 pm
Contact:

Re: [1.1.32]Reference counting in lua memory managing doesn't function properly.

Post by yagaodirac »

boskid wrote:
Wed Apr 21, 2021 5:52 am
First of all, thank you for such a long reply. It's very helpful.
I do recommend you to add some concrete example into your docs.
According to my test, all the functions, and metatable under global is gone after loading. All the variables outside the global( I guess this is what you call "the top level in control.lua") seem not safe if they are used as variable, I mean if they are changed after the first time assigned with anything.

So, my solution is something like this:
A variable c in the top level inside control.lua. Then, "c.class-name"(such as: c.vec2 ), then "c.class-name.func-name"(c.vec2.length). And all the classes have a field called "class_name" for each, which is the same as the class-name.(c.vec2.class_name = "vec2").
When it comes to new, I recommend 2 styles. One is new.vec2(x,y), the other is c.vec2.new(x,y). I guess both would probably work. Inside the constructor, to not to use the metatable feature of lua, the new object is always with a field named "c" to store the class name in string type.
The upper part describes the function part. They are setup within the compiling stage. After setup, I'm not gonna modify them anymore. It's something like the executable part inside an exe.
Then, the data part. As you mentioned, inside the script.on_init(), I could access the global variable. I setup the top level entrance here. I mean, they are logically top level, but not the top level when it comes to the compiling.
In this way, the script.on_load() hook could be ignored directly.

The code is like:

Code: Select all

c = c or {}   ------- c is short for class.
c.test_array = {}
c.test_array.class_name = "test_array"

c.test_array.add = function(this, element)     --------Member function.
	this.length = this.length +1
	rawset(this, this.length, element)
end

-------------- CTOR n DTOR ----------------
new = new or {}
new.test_array = function()         -------ctor
	local this = {}
	this.length = 0
	this.c = c.test_array.class_name
	return this
end
Then the control.lua

Code: Select all

require("test_array")        ---------- The include.

script.on_init(function ()
global = global or {}
global.cont = new.test_array()

end)

script.on_load(function ()
----------Nothing here.
end)

function call( this, func_name, param )    ----------An auxiliary function. Temporarily receives only 1 parameter.
	c[this.c][func_name](this, param)
end


script.on_event(defines.events.on_tick, function(event)
	if game.players[1].position.x<-2       -------- Trivial.
	then
		game.players[1].teleport(2)     -------- Trivial.
		log(serpent.block(global))
		log(serpent.block(c))
		log(serpent.block(new))
		log("--------------------------------------------------------")      -------- Trivial.
		
		--------Both ways of function call are the same with the auxiliary function.
		--c[global.cont.c].add(global.cont, game.tick)
		call(global.cont, "add", game.tick)    ------ I'm gonna redo my code in this style. Somehow it's still OOP-like enough.
	end
end)
This works through save load. And also works with server-client case.
Emmm. It's satisfying...

It's here now.
https://mods.factorio.com/mod/Multiplay ... OP-example

Thank you again. I guess my scenarios would works properly very soon.
谢谢,祝你身体健康,万事如意。

User avatar
boskid
Factorio Staff
Factorio Staff
Posts: 2739
Joined: Thu Dec 14, 2017 6:56 pm
Contact:

Re: [1.1.32]Reference counting in lua memory managing doesn't function properly.

Post by boskid »

You can simply use metatables on your objects stored inside of `global`. Metatables will be lost when save-load happens but you can traverse the `global` during the on_load() hook and restore the metatables on them. This will get rid of the "call()" function. Re-setup of metatables is one of intended use cases for the on_load() hook (step 6 in lifecycle)

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: [1.1.32]Reference counting in lua memory managing doesn't function properly.

Post by eradicator »

yagaodirac wrote:
Wed Apr 21, 2021 6:27 am

Code: Select all

global = global or {}
Note that this never does what you think it does. If your code doesn't work without that line it's broken. Factorio initialized and fills "global" for you before it raises on_init or on_load (whichever is first). Any prior data will be deleted at that point. You should never read or write to "global" outside of an event handler function, and you never need to initialize it manually.
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.

User avatar
eradicator
Smart Inserter
Smart Inserter
Posts: 5206
Joined: Tue Jul 12, 2016 9:03 am
Contact:

Re: [1.1.32]Reference counting in lua memory managing doesn't function properly.

Post by eradicator »

Also if you really want to use a generic OOP approach you could just recursively reapply the metatables. Though keep in mind that storing two additional strings (key + value) for every object instance is not very space-saving. It would be better to know which objects are located where and dynamically reattach the metatables on-use.

Code: Select all


/c

--[[recursive reapplicator]] 
local function reclassify(tbl, metatables)
  local function _reclassify(obj)
    if (type(obj) == 'table')
    and (not type(obj.__self) == 'userdata') --[[factorio object]]
    then
      setmetatable(obj, metatables[obj.__class]) --[[nil is fine too]]
      for k, v in pairs(obj) do
        _reclassify(k)
        _reclassify(v)
        end
      end
    end
  _reclassify(tbl)
  end
  
--[[class storage]] 
local metatables = {}

--[[example class]] 
local Vector = {}
metatables.vector = {__index = Vector}

--[[example init]] 
function Vector.new (x,y)
  return setmetatable({x = x, y = y, __class = 'vector'}, metatables.vector)
  end
  
--[[example method]] 
function Vector:add(x,y)
  self.x, self.y = self.x + x, self.y +y
  return self
  end

--[[example on_load]] 
script.on_load(function()
  reclassify(global, metatables)
  end)
  
script.on_nth_tick(30, function()
  if (global.pos == nil) then global.pos = Vector.new(0,0) end
  game.players[1].teleport(global.pos:add(1,1))
  end)
Author of: Belt Planner, Hand Crank Generator, Screenshot Maker, /sudo and more.
Mod support languages: 日本語, Deutsch, English
My code in the post above is dedicated to the public domain under CC0.

Post Reply

Return to “Modding help”