Thanks so much for the tips! This is definitely helping me understand scopes in LUA.
Since posting my original message I did find a solution to one of the first points you made regarding accessing globals from my DerivedObject environment. My original post said this...
And as you point out, I will have problems with the globals. I changed it to this...
... which I think accomplishes the
same thing as your suggestion.
That said, I really like the way you are registering the classes in the master table and handling the cleanup process. I am going to play around with this code. Thanks again!
----- Original Message ----
From: Thomas Lefort <thomas.lefort@gmail.com>
To: Lua list <lua@bazar2.conectiva.com.br>
Sent: Sunday, November 18, 2007 11:11:45 AM
Subject: Re: Sanity check needed on design
On Nov 15, 2007 7:14 PM, Buzz Burrowes <
buzz_burrowes@yahoo.com> wrote:
[...]
> Then... the system's users would derive their own types from that
base. Each
> type would be in a file that would something like this...
>
> DerivedObject = ObjectMaster:new{}
> DerivedObject._envrionment = {}
> setfenv(1, DerivedObject._envrionment)
>
> -- marker 1
>
> -- override base classes' func1...
> function DerivedObject:func1(v)
> self.var = v * 2.0
> end
If you try it, you will get:
attempt to index global 'DerivedObject' (a nil value)
This because the environment you set is empty. To correct this, you
can make it inherit globals with:
env = setmetatable({}, {__index = _G})
or equivalently in Lua 5.1:
env = package.seeall{}
> The code above is close to what I see in the docs in section 15 on
packages.
> Once these derived classes are "declared" the system will "spawn
instances"
> of these user classes by creating globals of the derived type from
the C++
> side.
However, you don't need to spawn objects into the global table (_G):
any table will do, and a dedicated table containing for sure only
those objects will be easier to manage.
> 1) I think that this will "sandbox" user classes so that the global
variable
> space will not get cluttered. This is accomplished with the setfenv
near the
> top of the file where the user defines his derived class. My hope is
that
> now, if the creator of the derived class inserts a "global" variable
> declaration at comment "marker 1" above it will go into the "local"
> environment.
Indeed global assignments will update the environment, not the
original globals. You can event catch such assignments by giving your
environment an adequate __newindex metamethod, so as to check and
collect them as declarations, for instance; or why not raise an error
message because you want to forbid any globals in user code (see my
tip on locals later).
> 2) I need to be able to easily cleanup these user classes / sandboxes
to be
> sure all memory is freed. I think that in this scenario I will be
able to
> easily clean up / unload user classes by setting all instances of the
> derived type to nil and the "definition" to nil. So... if I did
this...
>
> Obj1DerivedFromDerivedObject = nil
> Obj2DerivedFromDerivedObject = nil
> DerivedObject = nil
>
> ... the garbage collector would eventually completely clean up the
user's
> "object definition" and its instances.
That's the idea, assuming those globals are the only remaining
references of the objects. I still suggest you use a dedicated table
to keep track of objects, though. Then to clean up, you can simply
release any references of the table itself.
I would solve the whole problem along the following line:
---------------------------------------------------
-- DerivedObject.lua: sample user-defined subclass
DerivedObject = newUserClass "DerivedObject"
FakeGlobal = true
-- tip: a local parameter is cleaner and faster than a global one.
local myFactor = 2.0
function DerivedObject:func1(v)
self.var = v * myFactor
end
The newUserClass function takes a public name, derives a class from
ObjectMaster, registers it with a local table, and returns it to
the
chunk.
DerivedObject won't be global, it is only the private name of the
class. We could have used a shorter name for the sake of brevity.
FakeGlobal won't be global either.
---------------------------------------------------
-- framework.lua: sample implementation
local ObjectMaster = {}
function ObjectMaster:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
-- keep track of user-defined subclasses
local userClasses = {}
-- create and register a user class
local function newUserClass(name)
local o = ObjectMaster:new()
assert(not userClasses[name], "duplicate class name")
userClasses[name] = o
return o
end
-- environment metatable to expose the
newUserClass function
local env_meta = {
__index = {newUserClass = newUserClass}
}
-- load user chunk in a dedicated environment
function loadUserClass(filename)
local chunk = loadfile(filename)
local env = setmetatable({}, env_meta)
setfenv(chunk, env)
chunk()
return env
end
-- show user subclasses
function dump()
for name, class in pairs(userClasses) do
print(name, class)
end
end
-- release our references of user classes
function cleanup()
userClasses = {}
end
Let's try in the Lua shell. We start with an empty class table:
> dofile "framework.lua"
> dump()
Let's load a user class:
> env = loadUserClass
"DerivedObject.lua"
> dump()
DerivedObject table: 0x808a3d0
Good, a new <name, class> pair was registered with our local table.
Now let's have a look at the environment we used to run the user
chunk:
> for k, v in pairs(env) do print(k, v) end
DerivedObject table: 0x808a3d0
FakeGlobal true
DerivedObject maps to our class, it corresponds to our the first
assignment in our chunk. FakeGlobal comes from the second one. Let's
check that we meet our first requirement, i.e. the global namespace is
left uncluttered:
> assert(not DerivedObject)
> assert(not FakeGlobal)
Finally, let's release our references of user classes:
> cleanup()
> dump()
They're gone indeed. It is now easy to complete the framework with a
spawning function, left as an exercise ;-)
I hope this helps !
--
--
Thomas