lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


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...

> DerivedObject = ObjectMaster:new{}
> DerivedObject._envrionment = {}
> setfenv(1, DerivedObject._envrionment)

And as you point out, I will have problems with the globals. I changed it to this...

DerivedObject = ObjectMaster:new ()
DerivedObject ._envrionment = {}
setmetatable(DerivedObject ._envrionment, {__index = _G})
setfenv(1,DerivedObject ._envrionment);

... 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!

Best,
Buzz


----- 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



Never miss a thing. Make Yahoo your homepage.