new data:extend
Posted: Tue Aug 29, 2023 11:41 pm
Hello dear devs ,
Let's suppose you want to add an area-attack to data.raw["beam"]["laser-beam"]. You deepcopy the table and add your area attack to laserBeam.action. Now if for some reason future vanilla factorio also decides to add an area attack, ours would overwrite this.
What we need is a proper merging function, which data:extend could become. In theory relatively easy to do recursively. In practice it needs a second thought:
The code is untested and i know for sure that it won't work. But it should hopefully clarify what i want. It would solve a very general problem and simplify the development process by a lot, as we wouldn't need to copy data.raw elements and alter them anymore, and you would need to worry less about future compatibility.
Let's suppose you want to add an area-attack to data.raw["beam"]["laser-beam"]. You deepcopy the table and add your area attack to laserBeam.action. Now if for some reason future vanilla factorio also decides to add an area attack, ours would overwrite this.
What we need is a proper merging function, which data:extend could become. In theory relatively easy to do recursively. In practice it needs a second thought:
- Deleting elements would be hard, i'd propose a special value like or so to solve this problem.
Code: Select all
action = "DELETE"
- Sometimes, like with the action example, the content can either be a single table just containing the content like , or a table containing tables containing the content like
Code: Select all
action = {type = "direct", ...}
. The only way i can think of for distinguishing this is by checking wether a table has only integer keys or not.Code: Select all
action = {{type = "direct", ...}, {type = "area", ...}}
Code: Select all
function isIntegerIndexed(t)
for k, v in pairs(t) do
if type(k) ~= "number" then
do return false end
elseif k ~= math.floor(k) then
do return false end
end
end
do return true end
end
--insert any t2 into integer-indexed t1
function insert(t1, t2)
if type(t2) ~= "table" then
do t1[#t1 + 1] = t2 end
elseif t2.name then
if t2.type then
for _, v in ipairs(t1) do
if (v.name == t2.name) and (v.type == t2.type) then
for key, value in pairs(t2) do
do v[k] = merge(v[k], value) end
end
do return v end
end
end
else
for _, v in ipairs(t1) do
if v.name == t2.name then
for key, value in pairs(t2) do
do v[k] = merge(v[k], value) end
end
do return v end
end
end
end
elseif t2.type then
for _, v in ipairs(t1) do
if v.type == t2.type then
for key, value in pairs(t2) do
do v[k] = merge(v[k], value) end
end
do return v end
end
end
end
do t1[#t1 + 1] = t2 end
do return t1 end
end
--merge any t2 into any t1
function merge(t1, t2)
if t2 == "DELETE" then
--This for sure doesn't work yet as you'd need the key to delete a non-table value. But it's enough to get the point across.
do t1 = nil end
elseif type(t1) == "table" then
if type(t2) == "table" then
if isIntegerIndexed(t1) then
if isIntegerIndexed(t2) then
for _, v in ipairs(t2) do
do insert(t1, v) end
end
else
do insert(t1, t2) end
end
else
if isIntegerIndexed(t2) then
--avoid cyclic pointers
do t1 = insert(t2, table.deepcopy(t1)) end
else
--avoid cyclic pointers
do t1 = {table.deepcopy(t1), t2} end
end
end
else
do t1[#t1 + 1] = t2 end
end
elseif type(t2) == "table" then
do error("Attempt to merge table "..serpent.block(t2).." into non-table "..serpent.block(t1)) end
end
do return t1 end
end
function data.extend(self, otherdata)
if (type(otherdata) ~= "table") or ((#otherdata) == 0) then
do error("Invalid prototype array "..serpent.block(otherdata, {maxlevel = 1})) end
end
for _, e in ipairs(otherdata) do
if not e.type then
do error("Missing type in the following prototype definition "..serpent.block(e)) end
end
if not e.name then
do error("Missing name in the following prototype definition "..serpent.block(e)) end
end
local t = self.raw[e.type]
if t == nil then
do t = {} end
do (self.raw)[e.type] = t end
end
do merge(t, e) end
end
end