FactorioLua: Does an entity change it's adress over livetime?

Place to get help with not working mods / modding interface.
User avatar
ssilk
Global Moderator
Global Moderator
Posts: 12889
Joined: Tue Apr 16, 2013 10:35 pm
Contact:

FactorioLua: Does an entity change it's adress over livetime?

Post by ssilk »

My problem: I have lists of entities that are maintained in my code.
Now when an event comes in, I need to look up in general (not always), if that entity is already maintained or not.

About like so:

Code: Select all

...
function handle_my_event(event)
...
    index = find_index(event.entity)
    DO STUFF
...


function find_index(event_entity)
    for i, listed_entity in pairs(globals.mylist[event_entity.force.name])
        if listed_entity.valid and listed_entity == event_entity then
            return i -- the found index of that entry
        end
   end
Well. That list could be quite long (some hundred entries) and doing always such a search-for is simply spoken stupid, cause LUA supports hashes.
What I really want is something like

Code: Select all

...
function handle_my_event(event)
    local hash = SOME_HASH_FUNCTION_THAT_RETURNS_UNIQUE_HASH_FOR_ENTITY(event.entity)
    local listed_entity = globals.mylist[event.entity.force.name][hash]
    if listed_entity.valid then
            DO STUFF
   end
My idea for such a hash method is so:

Code: Select all

...
function handle_my_event(event)
    local hash = tostring(event.entity) -- generates something like "table: 0xAE7840048"
    local listed_entity = globals.mylist[event.entity.force.name][hash]
...
Using tostring() returns the memory-adress of that object as string. Cause two objects cannot exist at the same address this should work. I mean in pure Lua I have tested this and it works.

The question: Is such an entity to be guaranteed on the same memory address over the instance of a running game? And if the address changes, is that already handled by events?
I mean: Logically it is not, when a game is saved and loaded, but during a game a game-object should not change it's adress uselessly? So might be possible, or?

PS: The reason why I ask this is also, because when I manage several lists in my mod, that holds the same entity (one general list with all the entities, some optimized lists for different purposes), I need some kind of "Primary Key" for the entities, so that delete-operations can be really quick. Perhaps there is also a different/simpler approach?
Cool suggestion: Eatable MOUSE-pointers.
Have you used the Advanced Search today?
Need help, question? FAQ - Wiki - Forum help
I still like small signatures...
User avatar
aubergine18
Smart Inserter
Smart Inserter
Posts: 1264
Joined: Fri Jul 22, 2016 8:51 pm
Contact:

Re: FactorioLua: Does an entity change it's adress over livetime?

Post by aubergine18 »

Most player-creatable entities (buildings, belts, items, biters, pretty much everything you'll want to interact with) have a unit_number which is unique to that entity.

Remember also that you can store entities on global and their objects persist the save/load cycle.

Potentially, you could use entity object as the keys in a table (I've not had need to do this so haven't personally tested this):

myData[entityTable] = someData

The downside here is that you can't use weak-keyed tables (__mode = "k" on the metatable) to housekeep the table, so you'd always need to check entityTable.valid before processing associated data. Obviously the table could grow very large over time, so periodic pruning of deceased entities would be required:

for entity in pairs(myData) do
if not entity.valid then myData[entity] = nil end
end
Better forum search for modders: Enclose your search term in quotes, eg. "font_color" or "custom-input" - it prevents the forum search from splitting on hypens and underscores, resulting in much more accurate results.
User avatar
ssilk
Global Moderator
Global Moderator
Posts: 12889
Joined: Tue Apr 16, 2013 10:35 pm
Contact:

Re: FactorioLua: Does an entity change it's adress over livetime?

Post by ssilk »

Ah, ok. This is so hidden and all examples I found didn't use it.

Hm. I'm not very deep into lua (still learning, weaktables, metatables, I'm too old for such stuff :roll: :lol: ), is it possible with the metatables to return only valid objects and handle all that stuff, preferable as a silent running garbage collection per tick in background? :)
Cool suggestion: Eatable MOUSE-pointers.
Have you used the Advanced Search today?
Need help, question? FAQ - Wiki - Forum help
I still like small signatures...
User avatar
ssilk
Global Moderator
Global Moderator
Posts: 12889
Joined: Tue Apr 16, 2013 10:35 pm
Contact:

Re: FactorioLua: Does an entity change it's adress over livetime?

Post by ssilk »

I mean "possible in theory". Cause this Lua is kind of very interesting deepness for such a "simple" programming language; but I don't want to put days of work into some project like this, if it is more or less clear, that it not going to work.

My problem is for something like that is, that I cannot yet clearly estimate that; I'm still learning it and my way to learn a programming language is some difficult project like that, but I loose interest and will be pissed off, if it's a dead end. :)
Cool suggestion: Eatable MOUSE-pointers.
Have you used the Advanced Search today?
Need help, question? FAQ - Wiki - Forum help
I still like small signatures...
User avatar
Klonan
Factorio Staff
Factorio Staff
Posts: 5290
Joined: Sun Jan 11, 2015 2:09 pm
Contact:

Re: FactorioLua: Does an entity change it's adress over livetime?

Post by Klonan »

ssilk wrote:My problem: I have lists of entities that are maintained in my code.
Now when an event comes in, I need to look up in general (not always), if that entity is already maintained or not.
something like this would work right?

Code: Select all

function is_maintained(entity, list)
  for k, listed_entity in pairs (list) do
    if listed_entity == entity then
      return true
    end
  end
  return false
end
User avatar
aubergine18
Smart Inserter
Smart Inserter
Posts: 1264
Joined: Fri Jul 22, 2016 8:51 pm
Contact:

Re: FactorioLua: Does an entity change it's adress over livetime?

Post by aubergine18 »

I've not tested this code, but it should facilitate an easy way to create and maintain entity data tables.

Save following to a lua script, eg. 'entity-data.lua':

Code: Select all

-- create fallback id for entity
local function fallbackId( entity )
  local pos = entity.position
  return entity.name .. tostring( pos.x ) .. tostring( pos.y )
end

-- get unique id for entity
local function idFor( entity )
  return entity.unit_number or fallbackId( entity )
end

-- metatable
local mt = {}

-- getter
function mt.__index( self, entity )
  return type(entity) == 'table' and
    rawget( self, 'data' )[ idFor( entity ) ]
  or
    rawget( self, entity )
end

-- setter
function mt.__newindex( self, entity, value )
  if type(entity) == 'table' then
    self.data[ idFor(entity) ] = value
  else
    rawset( self, entity, value )
  end
end

-- initialise a data table
return function( tbl )
  if not tbl.data then
    tbl.data = {}
  end
  return setmetatable( tbl, mt )
end
Then in your other scripts:

Code: Select all

local entity_data = require 'entity-data'

local data -- forward reference

-- on first use of mod, create one or more data tables
script.on_init( function()
  global.myCache = global.myCache or {}
  global.another = global.another or {}
end )

-- enable entity_data metatables on load
script.on_load( function()
  entity_data( global.myCache )
  -- optional: create local (faster) ref to a data table:
  data = entity_data( global.another )
end )

-- housekeeping: remove dead entities from our data store
local entityDeathEvents = {
  defines.events.on_entity_died,
  defines.events.on_preplayer_mined_item,
  defines.events.on_robot_pre_mined
}
script.on_event( entityDeathEvents, function(event)
  local entity = event.entity
  -- Lua tables are weak-valued by default, so setting value to
  -- `nil` will remove element from hash
  global.myCache[entity] = nil
  data[entity] = nil
end )

-- sometime later...
local function doSomethingTo( enitty )
  -- some code
  data[entity] = someData -- set data
end

-- sometime later...
local function somethingElse( entity )
  local defaultData = { some = 'stuff' }
  local someData = data[entity] or defaultData -- get data
  -- some code
end
Hope that helps.
Better forum search for modders: Enclose your search term in quotes, eg. "font_color" or "custom-input" - it prevents the forum search from splitting on hypens and underscores, resulting in much more accurate results.
User avatar
ssilk
Global Moderator
Global Moderator
Posts: 12889
Joined: Tue Apr 16, 2013 10:35 pm
Contact:

Re: FactorioLua: Does an entity change it's adress over livetime?

Post by ssilk »

Klonan wrote:something like this would work right?
[/code]
for k, listed_entity in pairs (list) do
if listed_entity == entity then
...
[/code]
Yes, this works (of course). 8-)

But I don't need this code any longer. The tip with the unit_number works so far.

Here the current code to enter an entry:

Code: Select all

    global.watchlist[force.name][entity.unit_number] = entity
This is the code to get an item out:

Code: Select all

	if global.watchlist[force.name][unit_number] then
		log("[RT] Exists: " .. unit_number .. " force " .. force.name)
		local found_entity = global.watchlist[force.name][unit_number]
		if found_entity.entity.valid then
			log("[RT] Valid: " .. found_entity.manager .. found_entity.tracker )
			return unit_number
I need now to remove invalid/deleted objects and will/want do this in background after each change, perhaps one unit per tick? So this list will be not up-to-date only for some milliseconds (one second per 60 entries - which is ok, I think, cause I still check for invalid). Hm. Could be made faster, if there would be an event, which is fired, when an entity becomes invalid.
Besides that: I think this kind of table-access is really, really fast (near to C-programming). I like it.

Next comes to pack the functions for add(), find() and remove() (or better remove_invalid(), if I really do that in background?) into a metatable. Still try to understand the metatables - the basic concepts are not new to me, but they are somehow quirked in Lua. :) I think I need to program first some examples. :)
Cool suggestion: Eatable MOUSE-pointers.
Have you used the Advanced Search today?
Need help, question? FAQ - Wiki - Forum help
I still like small signatures...
User avatar
ssilk
Global Moderator
Global Moderator
Posts: 12889
Joined: Tue Apr 16, 2013 10:35 pm
Contact:

Re: FactorioLua: Does an entity change it's adress over livetime?

Post by ssilk »

Maybe interesting: viewtopic.php?f=34&t=34181 Metatables, NOT
Cool suggestion: Eatable MOUSE-pointers.
Have you used the Advanced Search today?
Need help, question? FAQ - Wiki - Forum help
I still like small signatures...
User avatar
Afforess
Filter Inserter
Filter Inserter
Posts: 422
Joined: Tue May 05, 2015 6:07 pm
Contact:

Re: FactorioLua: Does an entity change it's adress over livetime?

Post by Afforess »

Followup: ssilk, you may have figured this out already, but the Factorio Stdlib Project has a working implementation here: https://github.com/Afforess/Factorio-St ... entity.lua

Essentially, you can do things like Entity.set_data(entity_obj, { table of cool stuff }), and then retrieve it using the entity reference later on, with Entity.get_data(entity_obj). The data is saved in the global table, so will survive saves. It's unit tests & documented. In addition, it works whether or not entities have a unit_number or not.

To answer the original question in the OP: An entity's address (pointer) will change between save/loads, but the lua global references will be seamlessly updated behind the scenes with the new references, so mods should never notice (unless they are printing / logging the pointer addresses). So as far as Factorio modders are concerned an entity in a save will always be the same object, and obj == obj will always return true when comparing a saved reference in the global table to the new reference.
User avatar
ssilk
Global Moderator
Global Moderator
Posts: 12889
Joined: Tue Apr 16, 2013 10:35 pm
Contact:

Re: FactorioLua: Does an entity change it's adress over livetime?

Post by ssilk »

Of course I looked into that, but for my uses I need some kind of meta-information stored with the entity.
I also don't need to differ between entities with and without unit-number, I only use those, that have one.

Implementation of entity container looks currently like so:

Code: Select all

--- Entity Info
-- @type Entity Info
nttInfo = {
    manager = "", -- managers
    tracker = "", -- tracker-type: running, unmoved, fixed, once, random
    entity = false, -- ref to entity
}
... this meta-data-structure is used to generate an ntt, that is stored in the container - the filling of this is complex and depends on the current state of the entity - completly different code ...

Code: Select all

container = {}

container.set = function(ntt)
    if ntt.entity.valid then
        local unit_number = ntt.entity.unit_number
        local fn = ntt.entity.force.name
        container.ntt[unit_number] = ntt
        container.ntt4rc[fn][unit_number] = ntt
        container.ntttrkr[fn][ntt.tracker][unit_number] = ntt
        log("[RT] Added unit_number " .. unit_number .. " Force " .. fn .. " trkr: " .. ntt.tracker .. " manager " .. ntt.manager)
    else
        log("[RT] ERROR invalid unit_number: " .. unit_number)
    end
end

container.get = function(unit_number)
    local ntt = container.ntt[unit_number]
    if ntt.valid then
        return ntt
    end
end

container.get_by_force = function(unit_number, force_name)
    local ntt = container.ntt4rc[force_name][unit_number]
    if ntt.valid then
        return ntt
    end
end

container.get_all_tracker_by_force = function(tracker, force_name)
    return container.ntttrkr[force_name][tracker]
end

container.remove = function(unit_number)
    local ntt = container.ntt[unit_number]
    if ntt then
        local fn = ntt.entity.force.name
        container.ntttrkr[fn][ntt.tracker][unit_number] = nil
        container.ntt4rc[fn][unit_number] = nil
        container.ntt[unit_number] = nil
    end
end
You see: The same data is stored into three different structures. This is done for speed reasons. I didn't found a better way than hashtables. Open for better ideas. :)


Ah, and I have another question:

What I currently want to do is, that the container.ntt is renamed into global._ntt, etc.

What I want to know here is this: When I replace that code like so

Code: Select all

        global._ntt[unit_number] = ntt
        global._ntt4rc[fn][unit_number] = ntt
        global._ntttrkr[fn][ntt.tracker][unit_number] = ntt
How is the ntt stored then when saved? Is it a copy or is it a reference to the same table?
Cool suggestion: Eatable MOUSE-pointers.
Have you used the Advanced Search today?
Need help, question? FAQ - Wiki - Forum help
I still like small signatures...
Post Reply

Return to “Modding help”