Page 1 of 1

[Resolved] Weird BUG with my MOD - Mining Rail

Posted: Tue May 08, 2018 11:41 pm
by TheSAguy
Hi,

Someone reported the weirdest bug on my MOD that I've seen in some time.
When mining a section or rail, a section or rail far away from the mined rail also disappears.

Here is a GIF showing what happens: BUG

Image of issue:
BUG
I've attached a save. Anyone know what's going on here?
Seems like two rail pieces has the same Item ID or something.
I know with the release of 0.16.40 and then 0.16.41 some rail stuff got tweaked. Does that have something to do with it?

Would really appreciate it if someone could look at this.
Thanks.

Re: Weird BUG with my MOD - Mining Rail

Posted: Wed May 09, 2018 6:18 am
by Bilka
Could you also link/attach your mod.

Re: Weird BUG with my MOD - Mining Rail

Posted: Wed May 09, 2018 7:09 am
by darkfrei
Bilka wrote:Could you also link/attach your mod.
If you have the Savegame, you can sync all mods by one click.

Re: Weird BUG with my MOD - Mining Rail

Posted: Wed May 09, 2018 8:47 am
by Bilka
TheSAguy wrote: Seems like two rail pieces has the same Item ID or something.
I know with the release of 0.16.40 and then 0.16.41 some rail stuff got tweaked. Does that have something to do with it?
Well, you save your entities by position hashes (why? just use unit_number...), so no rail change should affect that. So, your global table is broken. There can be many reasons for that: A user messed with it, it didn't get saved properly, your mod has a bug somewhere, etc etc. If it is your mod, it could be anywhere you save, get, or delete entitites since you have everything in the same table.

Re: Weird BUG with my MOD - Mining Rail

Posted: Wed May 09, 2018 8:53 am
by Bilka
darkfrei wrote:
Bilka wrote:Could you also link/attach your mod.
If you have the Savegame, you can sync all mods by one click.
That doesn't work on mobile :P
Here are the relevant code parts for anybody who doesn't want to download a save and 55 mods:

Code: Select all

-- Power Rail on_built_entity
	if (entity.valid and entity.name == "bi-straight-rail-power") or (entity.valid and entity.name == "bi-curved-rail-power") then
	writeDebug("Power Rail has been built")
		local surface = entity.surface
		local force = entity.force
		local position = entity.position		   
		local rail_track = entity
		local pole_name = "bi-rail-hidden-power-pole"  		
		
		local create_rail_pole = surface.create_entity({name = pole_name, position = position, force = force})
				
		create_rail_pole.minable = false
		create_rail_pole.destructible = false 
		
		group_entities(cantor(position.x,position.y), { rail_track, create_rail_pole })	  

	end

Code: Select all

--- Power Rail has been removed
   	if (entity.valid and entity.name == "bi-straight-rail-power") or (entity.valid and entity.name == "bi-curved-rail-power") then
		local pos_hash = cantor(entity.position.x,entity.position.y)
        local entity_group = getGroup_entities(pos_hash)
        if entity_group then
            for ix, vx in ipairs(entity_group) do
                if vx == entity then
                    --vx.destroy()
                else
                    vx.destroy()
                end
            end
        end
        ungroup_entities(pos_hash)
	end

Code: Select all

-- Cantor Hash
-------------------------------------------------------------------
function cantor(k1, k2)
	return (0.5 * (k1+k2) * (k1+k2+1) + k2)
end

Code: Select all

function group_entities(entity_groupid, entity_list)
    return group("entities", entity_groupid, entity_list)
end

function getGroup_entities(entity_groupid)
    return getGroup("entities", entity_groupid)
end

function ungroup_entities(entity_groupid)
    return ungroup("entities", entity_groupid)
end

Code: Select all

-- Grouping
--
function group(index_id, group_id, members)
	_init_group(index_id, group_id)

	if index_id == nil then
	 index_id = "default"
 	end

	if group_id then
		for ix, vx in ipairs(members) do
			_addto_group(index_id, group_id, vx)
		end
		return group_id
	else
		-- no GID, then assign one
		return group(index_id, _new_group(index_id), members)
	end
end

function getGroup(index_id, group_id)
	_init_group(index_id, group_id)

	if group_id then
		return _get_group(index_id, group_id)
	else
		return nil
	end
end

function ungroup(index_id, group_id)
	_init_group(index_id, group_id)

	if group_id then
		return _clear_group(index_id, group_id)
	else
		return false
	end
end
-------------------------------------------------------------------
function _init_group(index_id)
	if not global.group then
		global.group = {}
	end
	if not global.group.default then
		global.group.default = {}
	end
	if index_id and not global.group[index_id] then
		-- don't care if they name their group "default"
		global.group[index_id] = {}
	end
end

function _addto_group(index_id, group_id, member_id)
	if global.group[index_id] then
		if not global.group[index_id][group_id] then
			global.group[index_id][group_id] = {}
		end
	end

	table.insert(global.group[index_id][group_id], member_id)
end

function _get_group(index_id, group_id)
	return global.group[index_id][group_id]
end

function _clear_group(index_id, group_id)
	global.group[index_id][group_id] = nil
end

Re: Weird BUG with my MOD - Mining Rail

Posted: Wed May 09, 2018 3:59 pm
by TheSAguy
Bilka wrote:
TheSAguy wrote: Seems like two rail pieces has the same Item ID or something.
I know with the release of 0.16.40 and then 0.16.41 some rail stuff got tweaked. Does that have something to do with it?
Well, you save your entities by position hashes (why? just use unit_number...), so no rail change should affect that. So, your global table is broken. There can be many reasons for that: A user messed with it, it didn't get saved properly, your mod has a bug somewhere, etc etc. If it is your mod, it could be anywhere you save, get, or delete entitites since you have everything in the same table.
Thanks for your feedback Bilka. I've always used the hashes, that was the way I was introduced to group entities.
What would be the optimal way to group entities and then remove them? (The code please :D )

I assume that any change that I make will have to be on a new game only...

Thanks!

Re: Weird BUG with my MOD - Mining Rail

Posted: Wed May 09, 2018 7:04 pm
by eradicator
Just use .unit_number for indexing instead of your hashing function. Unit_number is an integer unique to that entity, i.e. the same entity will always have the same unit_number even if the position changes. Position hashing was only used before unit_number was introduced in what...0.13?

Code: Select all

script.on_configuration_changed(function()
 global.newblob = {}
 for k,v pairs(your_global_blob) do
   global.newblob[v[1].unit_number] = v -- assumes v[1] is the element you want to index by.
   end
end)

Re: Weird BUG with my MOD - Mining Rail

Posted: Wed May 09, 2018 10:16 pm
by TheSAguy
eradicator wrote:Just use .unit_number for indexing instead of your hashing function. Unit_number is an integer unique to that entity, i.e. the same entity will always have the same unit_number even if the position changes. Position hashing was only used before unit_number was introduced in what...0.13?

Code: Select all

script.on_configuration_changed(function()
 global.newblob = {}
 for k,v pairs(your_global_blob) do
   global.newblob[v[1].unit_number] = v -- assumes v[1] is the element you want to index by.
   end
end)
Thanks eradicator, so I could use the above to, in theory, migrate old games to the new code.

For updating the code however, what is the best code to use?
Do I store it in global tables, like below?

Code: Select all

global.Arboretum_Table[create_arboretum.unit_number] = {inventory=create_arboretum, radar=create_radar}
and remove using:

Code: Select all

   	if entity.valid and entity.name == "bi-drill-base" then
		writeDebug("Drill has been removed")

		global.bi_drill_table[entity.unit_number].drill_bit.destroy()
		global.bi_drill_table[entity.unit_number] = nil
		
	end
I've looked at some other mods that add hidden entities, and it seems everyone does it differently. Just wandering what's the best way to do this, since I'm going to re-do it....

Thanks,.

Re: Weird BUG with my MOD - Mining Rail

Posted: Thu May 10, 2018 6:27 am
by Bilka
Just like in the game itself, there is no objectively best way to do this. But here is how I'd do it: Your general concept works, but has too much boilerplate for my taste. If you dont need the original entity in the table, don't add it. Separate tables for each type of entity because it allows you to easily remove one entity type, or change how it is saved.

So, for your rails, init the global in on_init/on_config_changed, to save global.rails[rail.unit_number] = power_pole would be enough, and when your rail is destroyed, get the power pole with the unit number, kill it, set the entry in the table nil. All of this can be done directly in the functions in like 5 lines instead of in some boilerplate code 5 files away. But you can still boilerplate it if you want. If you do need to save more than 1 entity, do global.labs[lab.unit_number] = {pump = pump, lamp = lamp}, like you did before.

There is probably a way that fits better to you/your coding style/your mod, but this is my opinion for how I'd do it.

Re: Weird BUG with my MOD - Mining Rail

Posted: Thu May 10, 2018 6:32 am
by eradicator
TheSAguy wrote: For updating the code however, what is the best code to use?
I've looked at some other mods that add hidden entities, and it seems everyone does it differently. Just wandering what's the best way to do this, since I'm going to re-do it....
I have no clue what your mod even does, much less what kind of data you need to store (your examples look like they're taken from several different mods to me). I would recommend you only change the indexing for now and keep the rest as is if there aren't any obvious/major problems with it (i.e. don't change for the sake of changing). That also reduces the risk of introducing new bugs while fixing this one.

Not sure how other mods do it, but i'm of the impression that everyone does it pretty much the same, as there's not much room for variation. You just dump the data you need into a global table. I.e. global.data_i_care_for[main_entity.unit_number] = {data1 = x, data2 = y, data3 = z}.