Good idea. However, it means you cant have a field in a table with the same name as an existing userdata. This forbids many keywords!
Ok, I see what you mean. How about I preface each keyword ie “uservec4”, “usermat4”. Edit: this method is handy with matrices where adding the keyword ruins the array (table.unpack stops working).
Is there a comprehensive list somewhere of all the kinds of “userdata” ? I guess it’s all the extra non-Lua APIs that Codea provides, eg Mesh, physics.body etc. I wonder how far this could be taken? Would be quite cool to be able to save a physics body or a mesh in this way. Will need to check whether you can iterate through all the values of a physics body or a mesh using pairs
…
Wow. I looked a way for a bit, and when I looked back, you two had beaten the problem into submission.
Thanks, @jmv38 and @yojimbo2000. Exactly what I wanted to see.
You can’t use pairs
on a mesh or a physics body, so you’d have to go through all the keys by hand. That’s not too bad I guess, a cut-and-paste job…
@Mark you’re welcome. I learned a lot. I’m new to meta-methods, don’t really use them in my own code. Depending on the complexity of the object, I think an encode exception function might be simpler (ie my last post above). If you have lots of different kinds of objects though, meshes, physics bodies etc, then meta methods is probably the way to go
@Jmv38 or how about 2 underscores before each special key, eg __mat4
?
great idea! I think you’ve nailed it.
On a related note, I’m having issues here: http://codea.io/talk/discussion/6679/how-do-i-get-the-meta-table-of-a-physics-body#latest
@Jmv38 any ideas?
Like that:
--# Main
function setup()
t = {[1]="a", [4]="b", [9]="c"}
t[10]="d"
t[5]="e"
print(json.encode(t))
t[13]="f"
print(json.encode(t))
t[11]="g"
t[12]="h"
print(json.encode(t))
end
On my iPad 2 it goes from list to dictionary and back to list:
["a",null,null,"b","e",null,null,null,"c","d"]
{"1":"a","9":"c","10":"d","4":"b","5":"e","13":"f"}
["a",null,null,"b","e",null,null,null,"c","d","g","h","f"]
@juce That's because JSON only allows string keys
. JSON works with numeric keys, see the example below. If I add a string key to tab1, it doesn’t. So it looks like you can have numeric keys or string keys, but not both in the same table. I haven’t needed to use json for anything, but just wanted to see how it worked.
function setup()
tab1={1,2,{7,8,9},3,4}
z=json.encode(tab1)
print(z)
tab2=json.decode(z)
z=json.encode(tab2)
print(z)
print("\
tab1 --------")
for a,b in pairs(tab1) do
print(a,b)
end
print("\
tab2 --------")
for a,b in pairs(tab2) do
print(a,b)
end
print("\
tab1 --------")
print(tab1[1])
print(tab1[3][1],tab1[3][2],tab1[3][3])
print("\
tab2 --------")
print(tab2[1])
print(tab2[3][1],tab2[3][2],tab2[3][3])
end
@Jmv38 this is my version of json encoding of a physics scene. You can save a snapshot of the scene, and then load it in again. Could be useful for a level editor in an Angry Birds style physics game:
https://gist.github.com/Utsira/da4fda9b95e9112d705a
Here’s the class with the various meta-methods:
--meta-methods
function AddToJson()
local meta = getmetatable(vec2())
meta.__tojson = function(t)
return json.encode({__vec2={x=t.x,y=t.y}})
end
meta = getmetatable(vec3())
meta.__tojson = function(t)
return json.encode({__vec3={x=t.x,y=t.y,z=t.z}})
end
meta = getmetatable(vec4())
meta.__tojson = function(t)
return json.encode({__vec4={x=t.x,y=t.y,z=t.z,w=t.w}})
end
meta = getmetatable(matrix())
meta.__tojson = function(t)
return json.encode({__mat4={t[1],t[2],t[3],t[4], t[5],t[6],t[7],t[8], t[9],t[10],t[11],t[12], t[13],t[14],t[15],t[16]}}) --a matrix.unpack function would've been handy here!
end
meta = getmetatable(color())
meta.__tojson = function(t)
return json.encode({__color={r=t.r, g=t.g, b=t.b, a=t.a}})
end
meta = getmetatable(mesh())
meta.__tojson = function(t)
return json.encode({nil}) --({__mesh=t.id})
end
local dummy = physics.body(CIRCLE, 10)
meta = getmetatable(dummy)
dummy:destroy()
meta.__tojson = function(t)
return json.encode({
__rigidbody={shapeType=t.shapeType, radius=t.radius, points=t.points},
__rigidbodyProperties={type=t.type, position=t.position, angle=t.angle, linearVelocity=t.linearVelocity, angularVelocity=t.angularVelocity, restitution=t.restitution, friction=t.friction, linearDamping=t.linearDamping, angularDamping=t.angularDamping
}})
end
--add extra categories from below according to needs
--gravityScale=t.gravityScale, info=t.info, density=t.density, sensor=t.sensor, categories=t.categories, mask=t.mask, fixedRotation=t.fixedRotation, active=t.active, awake=t.awake, sleepingAllowed=t.sleepingAllowed, interpolate=t.interpolate, joints=t.joints, bullet=t.bullet,
end
here is a possible solution. It is not ‘finished’ but you get the idea.
--# Main
-- Json Test
-- Use this function to perform your initial setup
function setup() --cant use vec2s
objects={
MyClass(100,500), MyClass(200,500), MyClass(400,500)
}
Vec2ToJson()
Vec3ToJson()
body2ToJson()
tabSave()
print("tap screen to load saved data")
end
function draw()
background(0)
for i,o in pairs(objects) do
o:draw()
end
end
function touched(t)
if t.state==BEGAN then
for i,o in pairs(objects) do
o.body:destroy()
end
objects = tabLoad()
end
end
function body2ToJson()
local b = physics.body(CIRCLE,10)
local meta = getmetatable(b)
b:destroy()
meta.__tojson = function(t)
local a = {Class="Body",x=t.x,y=t.y}
return json.encode(a)
end
Body = function()
return physics.body(CIRCLE,10)
end
end
function Vec2ToJson()
local meta = getmetatable(vec2())
meta.__tojson = function(t)
local a = {Class="vec2",x=t.x,y=t.y}
return json.encode(a)
end
end
function Vec3ToJson()
local meta = getmetatable(vec3())
meta.__tojson = function(t)
local a = {Class="vec3",x=t.x,y=t.y,z=t.z}
return json.encode(a)
end
end
function tabLoad()
local str=readProjectTab("Test")
str = string.sub(str,4,-4)
local t = json.decode(str)
t = checkForClass(t)
return t
end
function checkForClass(t)
if type(t) ~= "table" then
return t
end
for key,val in pairs(t) do
if type(val) == "table" then
t[key] = checkForClass(val)
end
local Class = t["Class"]
if Class then
local obj
if Class == "MyClass" then
obj = MyClass(t.body.x,t.body.y)
else
obj = _G[Class]()
end
for k,v in pairs(t) do
if k~="Class" then
obj[k] = v
end
end
t = obj
end
end
return t
end
function tabSave()
local str=json.encode(objects,{indent=true})
saveProjectTab("Test", "--["..str.."]--")
end
--# MyClass
MyClass = class()
function MyClass:init(x,y)
self.Class = "MyClass"
self.foo = "bar"
self.pos=vec2(50,50)
self.mis =vec3(1,2,3)
self.body = physics.body(CIRCLE,10)
self.body.x = x
self.body.y = y
end
function MyClass:__tostring()
return json.encode(self,{indent=true})
end
function MyClass:draw()
ellipse(self.body.x,self.body.y,self.body.radius)
end
--# Test
--[[{
"body":{"y":500.0,"x":100.0,"Class":"Body"},
"Class":"MyClass",
"pos":{"y":50.0,"x":50.0,"Class":"vec2"},
"mis":{"z":3.0,"y":2.0,"x":1.0,"Class":"vec3"},
"foo":"bar"
},{
"body":{"y":500.0,"x":200.0,"Class":"Body"},
"Class":"MyClass",
"pos":{"y":50.0,"x":50.0,"Class":"vec2"},
"mis":{"z":3.0,"y":2.0,"x":1.0,"Class":"vec3"},
"foo":"bar"
},{
"body":{"y":500.0,"x":400.0,"Class":"Body"},
"Class":"MyClass",
"pos":{"y":50.0,"x":50.0,"Class":"vec2"},
"mis":{"z":3.0,"y":2.0,"x":1.0,"Class":"vec3"},
"foo":"bar"
}]]--
@Jmv38 thanks for showing us how to set the toJson metamethod (for physics bodies too!)
@dave1707, you are right: i should’ve been more specific - json only allows string keys in dictionaries. The edge cases ultimately come from data structure differences: Lua’s table can have keys of arbitrary types and also has the notion of having an array part and a hash (dictionary) part, while json has two different container types: dictionary and list.
I think what happens is that json.encode looks for special case when encoding tables: if a table only has integer keys, with the smallest one being 1, then it converts such table to a list, putting nulls into missing slots. Otherwise, it converts the table into a dictionary, collating numeric keys into string keys.
--# Main
function setup()
t = {[1]="foo",[10]="bar"}
print(json.encode(t))
t = {[1]="foo",[20]="bar"}
print(json.encode(t))
t = {[-1]="foo",[0]="bar", [1]="baz"}
print(json.encode(t))
-- print array part of the table
for i,v in ipairs(t) do
print(i,v)
end
end
But even that description is not entirely accurate: notice how second table has “too many” nil slots, compared to first one - and gets converted into a dictionary, not a list.
@juce that’s interesting how it decides whether to go with dictionary or list. I avoid mixed dictionary-lists in Lua, it just seems like too much potential for unexpected behaviour
@juce Interesting. I guess you really need to know how the decode is going to handle a table that’s encoded. You could write a program that uses a table without any problems only to have it crash when you try to recreate a new table with decode.
@yojimbo2000, i agree that mixing numeric keys with keys of other types is dangerous, and best avoided if possible. However, even if we use only numeric keys, this table-to-list conversion algorithm can play unexpected tricks: say you have a sparse table with integer keys - it can probably change back and forth between list and dictionary, as you add more values to it, depending on the exact distribution of keys.
I also try to avoid sparse lists, by using table.remove
to make sure there’s no holes. @dave1707 try my code at the gist. I’ll put my neck on the block and say it’s reasonably stable at recreating a physics scene. :-SS
@dave1707, i think decode is ok, because it is fully deterministic. It’s the encoding step that can have unexpected results. But yes, the end result of encode + decode will not necessarily recreate the table as it was before.
@yojimbo2000 I’m not saying it won’t work and I’m sure yours does, but for someone who doesn’t understand how encode/decode works could be in for a surprise if they use it. Just like me, I thought encode/decode would take a table, convert it to a string that could be saved, then recreate the table exactly like the original. But it turns out there’s limits, even with simple tables. I don’t have any code that uses encode/decode and probably never will. It’s just that when it was brought up in this discussion, I thought I’d try it to see how it works and ran into some surprises that I pointed out above. If someone uses this in an App Store program without understanding everything about it, their program could crash.