Question about Classes/Objects and how they interact

I’m getting my head around using classes/objects using the inbuilt approach offered by LuaBind. However because Lua is so flexible with how you do things and doesn’t restrict you, I’m not sure I’m taking the right approach.

Consider the following code:

class 'Farm'
function Farm:__init()
  self.num_chickens = 0
end

function Farm:Add_Chicken()
  self.num_chickens = self.num_chickens + 1  
  return Chicken(self)
end

class 'Chicken'
function Chicken:__init(Farm)
  self.host_farm = Farm
  self.mother = false
end

function Chicken:Lay_Egg()
  return Egg(self)
end

class 'Egg'
function Egg:__init(Chicken)
  self.parent = Chicken
end

function Egg:Hatch()
  self.parent.mother = true
  return self.parent.host_farm:Add_Chicken()
end

-- Testing
old_mcdonald = Farm()
chicken = old_mcdonald:Add_Chicken()
egg = chicken:Lay_Egg()
new_chicken = egg:Hatch()

print(old_mcdonald.num_chickens) -- Should be 2
print(chicken.mother) -- Should be true
print(new_chicken.mother) -- Should be false

While this works, is this the right way of doing things?

It doesn’t seem right to me that that the ‘egg’ object should change the states of ‘chicken’ and ‘farm’ objects, but I can’t think of a better way to achieve the same outcome.

Just interested for my own understanding and wonder if any seasoned programmers could offer a view?

Thanks

I’ll take the bait as I’m seasoned with luabind…

It doesn’t seem right to me that that the ‘egg’ object should change the states of ‘chicken’ and ‘farm’ objects¨

Indeed it’s does not seem right that newly arrived eggs should notify the farms management. Seems a bit too bureaucratic :slight_smile:

Did you consider observable values/events? Basically, that the chicken laying the egg is an event that will notify the farm.

Of course, in code this requires that the farm register all the chickens, using some notifier method. But then this is also fixing what I see as a shortcoming for the code (you know the number of chickens but can not access any of them).

Ah yes, that would be better, so you would have a table in the Farm object that contained chickens and eggs…

I didn’t think of notifiers actually, so in real world terms this would be analogous to the farm worker regularly checking for eggs. How would you implement this? would you attach a ‘check for new eggs’ function to the app idle observable.

I suppose that would be less efficient than the previous method but less bureaucratic, I would prefer my Renoise Farm Simulatorto be free spirited :slight_smile:

How would you implement this? would you attach a ‘check for new eggs’ function to the app idle observable

Idle notifier should really only be used when you can’t work you way around the issue in other ways. Instead I would use the ObservableBang for this purpose.

Add it to the Chicken class and call it something like “on_egg_hatch”. Then, in the eggs Hatch method you can invoke it by calling self.parent.on_egg_hatch:bang()

To keep track of things, the Farm class would need to store Chicken instances in a table. Once created, you can then attach to the on_egg_hatchobservable, using on_egg_hatch:add_notifier (and some function that deals with this wonderful event).

To keep track of things, the Farm class would need to store Chicken instances in a table. Once created, you can then attach to the on_egg_hatchobservable, using on_egg_hatch:add_notifier (and some function that deals with this wonderful event).

Ok, great, after a bit of head scratching I get it.

So this works:

class 'Farm'
function Farm:__init()
  self.chickens = {}
end

function Farm:Add_Chicken()
  local chicken_index = #self.chickens+1
  self.chickens[chicken_index] = Chicken()
  self.chickens[chicken_index].on_egg_hatch:add_notifier( self.On_Egg_Hatch ) 
end

function Farm:On_Egg_Hatch()
  print("An egg has hatched!")
end

class 'Chicken'
function Chicken:__init()
  self.on_egg_hatch = renoise.Document.ObservableBang()
end

function Chicken:Lay_Egg()
  return Egg(self)
end

class 'Egg'
function Egg:__init(Chicken)
  self.parent = Chicken
end

function Egg:Hatch()
  self.parent.on_egg_hatch:bang()
end

-- Testing
old_mcdonald = Farm()
old_mcdonald:Add_Chicken()

egg = old_mcdonald.chickens[1]:Lay_Egg()
egg:Hatch()

However this doesn’t work if I try and call further Farm methods in the notifier function, probably because the notifier function has to be referenced using ‘self.On_Egg_Hatch’ rather than ‘self:On_Egg_Hatch()’

Therefore if I try this as the notifier function it doesn’t work:

function Farm:On_Egg_Hatch()
  print("An egg has hatched!")
  self:Add_chicken()
end

How would you get around this?

It’s a matter of scope. The add_notifier function accepts two arguments, an object (which acts as scope) and the function. Since you’re only passing the function, the scope is missing.

Method #1: specify the scope as an additional argument

self.chickens[chicken_index].on_egg_hatch:add_notifier(self,self.On_Egg_Hatch )

Method #2: specify the context within the anonymous function, like this

self.chickens[chicken_index].on_egg_hatch:add_notifier(function()
  self:On_Egg_Hatch()
end)

But this approach becomes problematic if you need toremove the notifiers at a later stage. Anonymous functions leaveno reference that we can use. For that reason, the first approach is probably always the best.

Excellent, that works :slight_smile:

Thank you very much, this has advanced my understanding a lot. Have always struggled to get my head around the Document API in the past and this helps a fair bit.

So how is this approach for cpu overhead? I’ve only ever used notifiers on renoise.song() _observable objects previously and if I remember correctly, create too many of those and things get a bit sluggish. Does the same apply for this approach? Y’know in case I want to scale up operations to industrial chicken farming.

I am not aware of any substantial penalty for doing this, unless the methods you call are doing something computationally expensive.

By far the heaviest notifier I know of is the line_notifier - it can get fired 100s of times when you hit e.g. Shift+F3.

But, even then, you can simply choose to handle this at a later stage by raising a flag that gets picked up by the idle notifier.

This approach is described here, in case you haven’t already seen it :slight_smile: