Log In  


One nice thing about objects is that objects set with the = operator or as arguments are all 'linked' meaning, among other things, you can provide them as arguments for a function call and that function will be able to edit the local copy of that object and have it affect the original one too. But sometimes, you don't want that. Sometimes you want to copy an object, make changes to it, and then discard the new copy without affecting the old one. That's why I've created a little helper function for this circumstance. Actually, I've created several. Here's the fully-featured 'default' function:

function cpy_obj(obj,recursion)
	if type(obj)=="table" then
		local return_value={}
		for k,v in pairs(obj) do
			if recursion then
				v=cpy_obj(v,true)
			end
			return_value[k]=v
		end
		return return_value
	else
		return obj
	end
end

Usage: copy= cpy_obj(𝘰𝘣𝘫𝘦𝘤𝘵) or function_call( cpy_obj(𝘰𝘣𝘫𝘦𝘤𝘵) )

return_value is the new object being created. Note that since we are creating it a piece at a time, it has no affiliation with the previous object. If you give it something that is not an object, it will just give you the input variable back. recursion is a boolean that asks if we want to make nested objects be decoupled as well. You usually want this if you have nested objects, but keep in mind that this comes with a performance cost, and although it's rather small, you might not want that if you're pushing up against the limits of PICO-8.

Variants

Every project is different, and with that comes different requirements. One person will be working on a game jam that has no risk of reaching the token limit, and another will be working on a 3D engine that needs to be as compact as possible. As such, I have a few different variants that may cater to your project's particular demands...

Forced recursion

function cpy_obj(obj)
	if type(obj)=="table" then
		local return_value={}
		for k,v in pairs(obj) do
			v=cpy_obj(v)
			return_value[k]=v
		end
		return return_value
	else
		return obj
	end
end


This one shaves off five tokens (plus one every time you need recursion!), but comes at the downside of a forced performance cost. Still, this one is probably closer to being the more useful one than the default one due to the token savings.

No recursion

function cpy_obj(obj)
	if type(obj)=="table" then
		local return_value={}
		for k,v in pairs(obj) do
			return_value[k]=v
		end
		return return_value
	else
		return obj
	end
end


This one saves five more tokens than the previous one, but lacks recursion entirely. Of course, you won't always (and usually don't) need recursion, but if you do then this function won't cut it for you.

No non-object return values

function cpy_obj(obj)
	local return_value={}
	for k,v in pairs(obj) do
		return_value[k]=v
	end
	return return_value
end


This one save TEN more tokens than even the last one, but non-object inputs will result in a crash as the API function pairs() won't know what to do.

Ultra-cheap snippet

𝘯𝘦𝘸_𝘰𝘣𝘫={}
for k,v in pairs(𝘰𝘭𝘥_𝘰𝘣𝘫) do
	𝘯𝘦𝘸_𝘰𝘣𝘫[k]=v
end


This one is cheaper than all of the others at just thirteen tokens, but must be copy-pasted everywhere you intend to use it meaning it only saves tokens if you only use it in one or two spots (which very well may be the case). Whatever you do, be sure to replace 𝘰𝘭𝘥_𝘰𝘣𝘫 and 𝘯𝘦𝘸_𝘰𝘣𝘫 with your object names.

Comparisons

Lastly, here's a list of the token costs of each function, assuming all arguments are one variable:

Default                     : 42 to define  , 3 or 4 per call
Forced recursion            : 37 to define  ,      3 per call
No recursion:               : 32 to define  ,      3 per call
No non-object return values : 22 to define  ,      3 per call
Ultra-cheap snippet         : no definition ,     13 per call


have you looked at prototypes? that’s how lua does inheritance, and is an interesting technique to know (imo better to have a good mental model of how one language works, rather than forcing concepts from other languages). see this:

function ref(tbl)
 return setmetatable({},
  --tbl = prototype of ^ table
  {__index=tbl})
end

function see(o)
 print(o.n,o.x,o.y)
end
cls()

orig={x=10,y=20,n="o"}
see(orig)

clone=ref(orig)
--search "x" in clone then orig
print(clone.x,2,120)
--set "x" in clone
clone.x=70
clone.n="c"
see(clone)

--this works!
clone.y+=32
--because it means this, which
--searches in clone>orig and
--sets in orig
clone.y=clone.y+32
see(clone)

in this approach

  • creating tables is fast because there is no copy
  • function is very few tokens
  • prototypes can be chained
  • the cost of finding properties is paid at each clone.prop access
  • it’s very dynamic, you can add or can change values on the prototype and see that reflected when accesing the property on descendent tables (as long as the prop wasn’t set there)

this is a good page with more background for this concept (and a great book too): https://gameprogrammingpatterns.com/prototype.html



[Please log in to post a comment]