Jump to content

Single-state Lua


Josh
 Share

Recommended Posts

Lua script:

referencetable={}

reference={}

referencetable[3]=reference

 

function reference:test()

print("hello")

end

 

 

Now how would you call reference:test() from the main program?:

 

lua_getglobal("referencetable")

lua_getfield()? //Need to find the table for the value "3".

lua_getfield("test") //okay

Now we have to push the reference table for the test function to use. How?

 

You cannot get the global variable "reference" because it is just one of many tables the routine will create.

 

lua_getfield uses a string to find the value, so if I use an object as the index it doesn't work:

function CreateClass(modelreference)

class={}

classtable[modelreference]=class

end

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

lua_getglobal(L, "referencetable") ' I'm assuming the "reference" table should be a local here, so not in the globals table
lua_pushnumber(L, 3) ' push the key
lua_gettable(L, -2) ' get the value

' or with an object:
lua_getglobal(L, "classtable") ' _G["classtable"]
lua_pushbmaxobject(L, modelreference) ' modelreference
lua_createtable(L, 0, 0) ' {}
lua_settable(L, -3) ' classtable[modelreference]={}
lua_pop(L, 1) ' remove the table from classtable from the stack

 

Not sure I see the problem. Maybe you're mistakenly thinking that the only way to get values out of/put values in a table is with lua_get/setfield?

MacBook Pro 15.4", Intel Core 2 Duo T9600 2.80GHz, 4GB 1066MHz DDR3, GeForce 9400M 256MB, GeForce 9600M GT 512MB - Mac OS 10.6.2, Vista32

Link to comment
Share on other sites

Maybe you're mistakenly thinking that the only way to get values out of/put values in a table is with lua_get/setfield?

Yep.

 

I am not sure why lua_createtable() is being called above.

 

Here's how I would like it to work:

classtable={}
entitytable={}

function CreateClass(modelreference)
local class={}
classtable[modelreference]=class
class.instances={}

function class:Spawn(model)
	local entity={}
	entitytable[model]=entity
	entity.class=self
	self.instances[model]=entity

	function entity:SetKey(key,value)
		return 1
	end

	return entity
end

return class
end

 

The lua table "entity" corresponds to a BlitzMax model. The lua table "class" corresponds to a BlitzMax modelreference. Just as the BlitzMax modelreference has a list of all instances of that object, the Lua class table has an "instances" table listing all the Lua entities of that class. All shared data that the entities use would get stored in the class table. When all instances of the model are deleted, the engine calls a cleanup function, removing the class for that modelreference from the table. At that time, any sounds or other stuff loaded for the class will be freed.

 

Each "entity" table is also stored in a global table along with the BlitzMax model it corresponds to, so the engine can quickly find the entity table associated with any model.

 

Now let's say I wanted to call the SetKey() function on a specific model from the main program. The Lua tables for the class and entity are already set up, and I am ready to call a function:

lua_getglobal(L,"entitytable")
if lua_istable(L,-1)
lua_pushbmaxobject(L, model)
...?
EndIf

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

I'm making a bunch of assumptions about how you're doing this, but I'm going to assume I'm right.

 

' doing this longhand so exactly what is going in is more apparent (synonymous with lua_getglobal(L, "entitytable"))
lua_pushstring(L, "entitytable")		' stack =>  -1 = "entitytable"
lua_gettable(L, LUA_GLOBALSINDEX)		' stack =>  -1 = entitytable ref

lua_pushbmaxobject(L, model)			' stack =>  -2 = entitytable ref, -1 = model object
' get the "entity" table stored in "entitytable" (similar names make these examples confusing as hell)
lua_gettable(L, -2)						' stack =>  -2 = entitytable ref, -1 = entity ref
' remove the "entitytable" reference since it's unneeded
lua_remove(L, -2)						' stack =>  -1 = entity ref

' longhand (synonymous with lua_getfield(L, "SetKey", -2))
lua_pushstring(L, "SetKey")				' stack =>  -2 = entity ref, -1 = "SetKey"
' get SetKey method
lua_gettable(L, -2)						' stack =>  -2 = entity ref, -1 = function

' then push the "entity" table to the top of the stack so it will be the 'self' parameter
lua_pushvalue(L, -2)					' stack =>  -3 = entity ref, -2 = function, -1 = entity ref (self argument)
' remove entity ref at -3
lua_remove(L, -3)						' stack =>  -2 = function, -1 = entity ref (self argument)

' push a key and value

' by now the stack should be something like  -4 = function, -3 = entity ref (self argument), -2 = key argument, -1 = value argument
lua_pcall(L, 3, 0, 0)					' stack =>  either nothing left or -1 = error string if there was an error

 

The code highlighting on this forum desperately needs a 'turn the hell off' option.

MacBook Pro 15.4", Intel Core 2 Duo T9600 2.80GHz, 4GB 1066MHz DDR3, GeForce 9400M 256MB, GeForce 9600M GT 512MB - Mac OS 10.6.2, Vista32

Link to comment
Share on other sites

Yeah, the highlighting is for C and I haven't found a way to change it yet.

 

This makes more sense now. Still confusing, but if I follow it carefully it should work.

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

I'm getting some good results now.

 

-The only special function name is the the class creation function, i.e. light_directional_CreateClass().

-I started using "object" instead of "entity" to avoid confusing Lua tables with engine entities.

-You can call functions like object.super:Kill() in the object:Kill() method.

-The commands are all OO and contained in the class table.

-Objects can call each others' commands like player.enemy.weapon:shoot()

 

Here's a look at the light_directional script:

require("scripts/class")

function light_directional_CreateClass(modelreference)
local class=CreateClass(modelreference)

function class:InitDialog(grid)
	self.super:InitDialog(grid)
	group=grid:AddGroup("Light")
	group:AddProperty("Resolution",PROPERTY_CHOICE,"256,512,1024,2048")
	group:AddProperty("linearoffset",PROPERTY_VEC3,"0,1,2","Linear offset" )
	group:AddProperty("shadowdistance",PROPERTY_VEC3,"","Shadow distance" )
	group:AddProperty("Range",PROPERTY_FLOAT)
	group:Expand(1)		
end

function class:Spawn(model)
	local object=self.super:Spawn(model)
	object.model:SetKey("resolution","2")
	object.light=CreateDirectionalLight(object.model)		

	function object:SetKey(key,value)
		if key=="resolution" then
			if value=="0" then
				self.light:SetShadowmapSize(256)
			elseif value=="1" then
				self.light:SetShadowmapSize(512)
			elseif value=="2" then
				self.light:SetShadowmapSize(1024)
			elseif value=="3" then
				self.light:SetShadowmapSize(2048)
			end
		else
			return self.super:SetKey(key,value)
		end
	end

	function object:GetKey(key,value)
		if key=="linearoffset" then
			return self.light:GetShadowOffset(0,0)..","..self.light:GetShadowOffset(0,1)..","..self.light:GetShadowOffset(0,2)
		elseif key=="shadowdistance" then
			return self.light:GetShadowDistance(0)..","..self.light:GetShadowDistance(1)..","..self.light:GetShadowDistance(2)
		else
			return self.super:GetKey(key,value)
		end
	end

	function object:Kill(model)
		if self.light~=nil then
			self.light:Free()
			self.light=nil
		end
		self.super:Kill()
	end

	return object
end

return class
end

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Is there any way that can be cleaned up somehow? 3 nested functions SetKey() and everything needs to be in a nested function Create() just seems kind of messy. Even if this is how it has to be, there might be a way to hide some of this so it looks more straight forward.

Link to comment
Share on other sites

Is there any way that can be cleaned up somehow? 3 nested functions SetKey() and everything needs to be in a nested function Create() just seems kind of messy. Even if this is how it has to be, there might be a way to hide some of this so it looks more straight forward.

What would your solution be?

 

Personally, I see nothing wrong with this, aside from a bit of unnecessary code. E.g., in SetKey, all you'd need is this:

function object:SetKey(key,value)
if key=="resolution" then
	local value = tonumber(value)
	if 0 <= power and power <= 3 then object.light:SetShadowmapSize(256 * 2^power) end
else
	return self.super:SetKey(key,value)
end
end

This probably comes down to coding style, however, but I like having fewer lines. Far as I'm concerned, nested functions in Lua are fine. You wouldn't have to put everything in Create, for one - you could store it in a local and reference that in the function, like so:

 

require "scripts/class"

do

local function table_merge(table, into)
	for k,v in next, table, nil do
		into[k]=v
	end
end

-- object methods
local object_methods = {}

function object_methods:SetKey(key,value)
	if key=="resolution" then
		local power = tonumber(value)
		if 0 <= power and power <= 3 then object.light:SetShadowmapSize(256 * 2^power) end
	else
		return self.super:SetKey(key,value)
	end
end

function object_methods:GetKey(key,value)
	if key=="linearoffset" then
		return object.light:GetShadowOffset(0,0)..","..object.light:GetShadowOffset(0,1)..","..object.light:GetShadowOffset(0,2)
	elseif key=="shadowdistance" then
		return object.light:GetShadowDistance(0)..","..object.light:GetShadowDistance(1)..","..object.light:GetShadowDistance(2)
	else
		return self.super:GetKey(key,value)
	end
end

function object_methods:Kill(model)
	if self.light then
		self.light:Free()
		self.light = nil
	end
	self.super:Kill()
end


-- class methods
local class_methods = {}

function class_methods:InitDialog(grid)
	self.super:InitDialog(grid)
	group=grid:AddGroup("Light")
	group:AddProperty("Resolution",PROPERTY_CHOICE,"256,512,1024,2048")
	group:AddProperty("linearoffset",PROPERTY_VEC3,"0,1,2","Linear offset" )
	group:AddProperty("shadowdistance",PROPERTY_VEC3,"","Shadow distance" )
	group:AddProperty("Range",PROPERTY_FLOAT)
	group:Expand(1)	 
end

function class_methods:Spawn(model)
	local object=self.super:Spawn(model)
	object.model:SetKey("resolution","2")
	object.light=CreateDirectionalLight(object.model)	       

	table_merge(object_methods, object)

	return object
end


-- light_directional_CreateClass
function light_directional_CreateClass(modelreference)
	local class=CreateClass(modelreference)
	table_merge(class_methods, class)
	return class
end

end

MacBook Pro 15.4", Intel Core 2 Duo T9600 2.80GHz, 4GB 1066MHz DDR3, GeForce 9400M 256MB, GeForce 9600M GT 512MB - Mac OS 10.6.2, Vista32

Link to comment
Share on other sites

eh... personally like Josh's way better... its easier for me trying to follow along... but its like you said a matter of coding style.

 

question: where is 'power' being defined at? should it be:

function object:SetKey(key,value)
       if key=="resolution" then
               local power = tonumber(value)
               if 0 <= power and power <= 3 then object.light:SetShadowmapSize(256 * 2^power) end
       ... etc...

Win7 64bit / Intel i7-2600 CPU @ 3.9 GHz / 16 GB DDR3 / NVIDIA GeForce GTX 590

LE / 3DWS / BMX / Hexagon

macklebee's channel

Link to comment
Share on other sites

I agree, fewer lines that I have to type the better. Your example of how to do it however isn't much fewer. I was more thinking of some kind of preprocessor macro stuff to make things look easier. Not sure if lua has that sort of thing.

A preprocessor sounds like a horrifyingly bad idea.

 

question: where is 'power' being defined at? should it be:

Woops, editing stuff in the post window tends to do that.

MacBook Pro 15.4", Intel Core 2 Duo T9600 2.80GHz, 4GB 1066MHz DDR3, GeForce 9400M 256MB, GeForce 9600M GT 512MB - Mac OS 10.6.2, Vista32

Link to comment
Share on other sites

Well, there's the easy way and the powerful way. The multistate approach is very procedural and easy on the eyes, but the single state approach has to have things contained within the class table. You can't just declare a bunch of variables and not worry about them interfering with other scripts. I think if I tried to move the object functions to another file or another part of the code, it would be more confusing. If I tried to muddle it up and make it prettier I think I would just be obfuscating what is really happening.

 

I'm pretty satisfied because it's object-oriented and still pretty simple. I am pretty optimistic about this design so I will document it extensively.

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

What is happening there?

 

I assume the editor calls light_directional_CreateClass(). Which defines the other functions in the class table and then returns the class table to the editor, which then can call the functions defined inside light_directional_CreateClass? So I assume the editor will save off the class table per model in the scene so it can call the functions defined in it. It's not the worst just seems the light_directional_CreateClass() function should be more of a class. I'm just used to C++ more than lua, but I get what it's doing.

 

Do we have access to the preprocessor? I might try to make something just for myself and the scripts I create.

Link to comment
Share on other sites

There is no preprocessor.

 

How would light_directional_CreateClass() be a class? If you have any ideas tell me.

 

I could eliminate the big class function and just set the value of modelreference directly from the engine before the script is run. That gets rid of one layer of nesting:

require("scripts/class")

local class=CreateClass(modelreference)

function class:InitDialog(grid)
self.super:InitDialog(grid)
group=grid:AddGroup("Light")
group:AddProperty("Resolution",PROPERTY_CHOICE,"256,512,1024,2048")
group:AddProperty("linearoffset",PROPERTY_VEC3,"0,1,2","Linear offset" )
group:AddProperty("shadowdistance",PROPERTY_VEC3,"","Shadow distance" )
group:AddProperty("Range",PROPERTY_FLOAT)
group:Expand(1) 	
end

function class:Spawn(model)
local object=self.super:Spawn(model)
object.model:SetKey("resolution","2")
object.light=CreateDirectionalLight(object.model) 	

function object:SetKey(key,value)
	if key=="resolution" then
		if value=="0" then
			self.light:SetShadowmapSize(256)
		elseif value=="1" then
			self.light:SetShadowmapSize(512)
		elseif value=="2" then
			self.light:SetShadowmapSize(1024)
		elseif value=="3" then
			self.light:SetShadowmapSize(2048)
		end
	else
		return self.super:SetKey(key,value)
	end
end

function object:GetKey(key,value)
	if key=="linearoffset" then
		return self.light:GetShadowOffset(0,0)..","..self.light:GetShadowOffset(0,1)..","..self.light:GetShadowOffset(0,2)
	elseif key=="shadowdistance" then
		return self.light:GetShadowDistance(0)..","..self.light:GetShadowDistance(1)..","..self.light:GetShadowDistance(2)
	else
		return self.super:GetKey(key,value)
	end
end

function object:Kill(model)
	if self.light~=nil then
			self.light:Free()
			self.light=nil
	end
	self.super:Kill()
end

return object
end

class=nil

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Replace the 'class=nil' bit at the end of that script with 'return class' and use luaL_loadfile like so:

 

lua_pushstring(L, entity_class_name+"_CreateClass") ' e.g., light_directional
luaL_loadfile(L, path_to_entity_script) ' loads the script as a Lua function
lua_settable(L, LUA_GLOBALSINDEX) ' sets the global light_directional_CreateClass to the function

MacBook Pro 15.4", Intel Core 2 Duo T9600 2.80GHz, 4GB 1066MHz DDR3, GeForce 9400M 256MB, GeForce 9600M GT 512MB - Mac OS 10.6.2, Vista32

Link to comment
Share on other sites

And the application end is like this? It doesn't look like modelreference is being passed correctly:

						Local result:Int
					Local size:Int=modelref.lua.StackSize()
					lua_pushstring(modelref.lua.L,modelref.prefix+"CreateClass")
					result=modelref.lua.loadfile(StripExt(modelref.path)+".lua")
					If result
						lua_settable(modelref.lua.L,LUA_GLOBALSINDEX)
					EndIf
					modelref.lua.SetStackSize(size)
					If result
						size=modelref.lua.StackSize()
						lua_getglobal(modelref.lua.L,modelref.prefix+"CreateClass")
						If lua_isfunction(modelref.lua.L,-1)
							modelref.lua.pushobject(modelref)
							modelref.lua.invoke(1,0)
						EndIf
						modelref.lua.SetStackSize(size)
					EndIf

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

I've never touched Lua before LeadWerks (I'm a python guy myself), but the single-state code you posted was simple enough for even me to get. If it makes communicating between entities easier and adds flexibility, then my vote is to go w/ single-state.

 

Niosop

Windows 7 x64 - Q6700 @ 2.66GHz - 4GB RAM - 8800 GTX

ZBrush - Blender

Link to comment
Share on other sites

Forgot about the modelreference bit. Add local modelreference = ... just after the require line. Could also do local class = CreateClass(...).

 

Also, when checking the result of luaL_loadfile, luaL_dofile, etc. you check to see if the result is zero, not if it's non-zero (so, result=0 means no errors), so that's also an error in your code there.

 

Out of curiosity, Josh, do you have a Google Wave account?

MacBook Pro 15.4", Intel Core 2 Duo T9600 2.80GHz, 4GB 1066MHz DDR3, GeForce 9400M 256MB, GeForce 9600M GT 512MB - Mac OS 10.6.2, Vista32

Link to comment
Share on other sites

require("scripts/class")

local modelreference=...

local class = CreateClass(modelreference)

function class:InitDialog(grid)
self.super:InitDialog(grid)
group=grid:AddGroup("Light")
group:AddProperty("Resolution",PROPERTY_CHOICE,"256,512,1024,2048")
group:AddProperty("linearoffset",PROPERTY_VEC3,"0,1,2","Linear offset" )
group:AddProperty("shadowdistance",PROPERTY_VEC3,"","Shadow distance" )
group:AddProperty("Range",PROPERTY_FLOAT)
group:Expand(1)		
end

function class:Spawn(model)
local object=self.super:Spawn(model)
object.model:SetKey("resolution","2")
object.light=CreateDirectionalLight(object.model)		

function object:SetKey(key,value)
	if key=="resolution" then
		if value=="0" then
			self.light:SetShadowmapSize(256)
		elseif value=="1" then
			self.light:SetShadowmapSize(512)
		elseif value=="2" then
			self.light:SetShadowmapSize(1024)
		elseif value=="3" then
			self.light:SetShadowmapSize(2048)
		end
	elseif key=="range" then
		self.light:SetRange(value)
	elseif key=="shadowdistance" then
		local offset=string.Explode(value,",")
		x=tonumber(offset[1])
		y=tonumber(offset[2])
		z=tonumber(offset[3])
		if x==nil then x=0 end
		if y==nil then y=0 end
		if z==nil then z=0 end
		self.light:SetShadowDistance(x,0)
		self.light:SetShadowDistance(y,1)
		self.light:SetShadowDistance(z,2)	
	elseif key=="linearoffset" then
		local offset=string.Explode(value,",")
		x=tonumber(offset[1])
		y=tonumber(offset[2])
		z=tonumber(offset[3])
		if x==nil then x=0 end
		if y==nil then y=0 end
		if z==nil then z=0 end
		self.light:SetShadowOffset(x,1.0,0)
		self.light:SetShadowOffset(y,1.0,1)
		self.light:SetShadowOffset(z,1.0,2)
	else
		return self.super:SetKey(key,value)
	end
end

function object:GetKey(key,value)
	if key=="linearoffset" then
		return self.light:GetShadowOffset(0,0)..","..self.light:GetShadowOffset(0,1)..","..self.light:GetShadowOffset(0,2)
	elseif key=="shadowdistance" then
		return self.light:GetShadowDistance(0)..","..self.light:GetShadowDistance(1)..","..self.light:GetShadowDistance(2)
	elseif key=="range" then
		return self.light:GetRange()
	elseif key=="shadowresolution" then
		resolution=self.light:GetShadowmapSize()
		if resolution==256 then
			return 0
		elseif resolution==512 then
			return 1
		elseif resolution==1024 then
			return 2	
		elseif resolution==2048 then
			return 3
		else
			return -1
		end
	else
		return self.super:GetKey(key,value)
	end
end

function object:Kill(model)
	if self.light~=nil then
		self.light:Free()
		self.light=nil
	end
	self.super:Kill()
end

return object
end

return class

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

I don't see anything in the script that would cause that error, and assuming your Lua type's methods are essentially one-to-one wrappers around the existing API, I don't know that there's a problem with that. Probably an error in another script.

MacBook Pro 15.4", Intel Core 2 Duo T9600 2.80GHz, 4GB 1066MHz DDR3, GeForce 9400M 256MB, GeForce 9600M GT 512MB - Mac OS 10.6.2, Vista32

Link to comment
Share on other sites

No, I have it pretty well isolated. It occurs when I try to do anything with modelreference after it is set to ...

 

I'll keep messing around with it.

Try adding error("<" .. type(modelreference) .. "> " .. tostring(modelreference), 1) right after you set modelreference. I'd be interested in knowing exactly what shows up as the error message there.

MacBook Pro 15.4", Intel Core 2 Duo T9600 2.80GHz, 4GB 1066MHz DDR3, GeForce 9400M 256MB, GeForce 9600M GT 512MB - Mac OS 10.6.2, Vista32

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...