Closed catfact closed 6 years ago
it took a minute to figure out, so here's a clean implementation of custom getter/setter/constructor methods for a class called Foo
with a property called bar
:
-- test some object oriented methods using metatables
-- metatable for class
local Foo = {}
Foo.__index = Foo
-- constructor without .new
setmetatable(Foo, {
__call = function (cls, ...)
return cls.new(...)
end
})
-- the "real" constructor
function Foo.new(val)
local self = setmetatable({}, Foo)
-- keep object state (aka properties, members) in this "private" table
self.state = { bar = val }
return self
end
-- custom getter
function Foo:__index(idx)
if idx == "bar" then
print( "getting " .. idx )
return self.state[idx]
else
return rawget( self, idx )
end
end
-- custom setter
function Foo:__newindex(idx, val)
if idx == "bar" then
self.state[idx] = val
print( "setting "..idx.." : "..val )
else
rawset( self, idx, val )
end
end
return Foo
custom setter is important for this approach; for example we would want to do stuff like:
-- get a Poll object
p = Poll('amp_in_l')
if p == nil then print("warning: unknown poll label; this Poll won't do anything")
-- set a callback, which is a normal member property
p.callback = function(val) print(val) end
-- set the callback period; this is a custom setter that calls poll_set_time under the hood
p.period = 0.02
-- instance method; calls the poll_start() C function under the hood
p:start()
the example code at the end is perfect for end-user script manipulation. this sort of abstraction is exactly what i was looking for.
On Wed, Oct 25, 2017 at 1:10 AM, ezra buchla notifications@github.com wrote:
it took a minute to figure out, so here's a clean implementation of custom getter/setter/constructor methods for a class called Foo with a property called bar:
-- test some object oriented methods using metatables local Foo = {} Foo.index = Foo -- constructor without .newsetmetatable(Foo, { call = function (cls, ...) return cls.new(...) end }) -- custom getterfunction Foo:index(idx) if idx == "bar" then print( "getting " .. idx ) return self.state[idx] else return rawget( self, idx ) endend -- custom setterfunction Foo:newindex(idx, val) if idx == "bar" then self.state[idx] = val print( "set member "..idx.." to "..val ) else rawset( self, idx, val ) endend -- the real constructorfunction Foo.new(val) local self = setmetatable({}, Foo) self.state = { bar = val } return self endidL return Foo
custom setter is important for this approach; for example we would want to do stuff like:
-- get a Poll object p = Poll('amp_in_l') -- set a callback, which is a normal member property p.callback = function(val) print(val) end-- set the callback period; this is a custom setter that calls poll_set_time under the hood p.period = 0.02-- instance method; calls the poll_start() C function under the hood p:start()
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/catfact/norns/issues/38#issuecomment-339216341, or mute the thread https://github.com/notifications/unsubscribe-auth/AAPEcNlUMolARPoWqcETMGt2547XTfYMks5svsLbgaJpZM4QFcMb .
cool - yeah, i'm rewriting all the lua now with a clearer understanding both of the language and what we need from it.
doing what i should have done in the first place, which is write the script first against imaginary API, and then work backwards.
here's one idea i'm working with right now, lmk what you think. it should do the following things:
local norns = require 'norns'
local engine = require 'engine'
local poll = require 'poll'
local input = require 'input'
local state = {
use_pitch = false,
pitch_poll = nil
gamepad = nil
}
local butCode = 'BTN_SOUTH'
norns.deinit = function()
state.pitch_poll.stop
state = nil
gamepad.unsetCallback(butCode)
end
local didLoadEngine = function(commands)
-- get the
local p = poll.findByLabel('pitch_l')
p.time = (0.25)
p.callback = function( hz )
if state.on then e.hz(hz) end
end
state.pitch_poll = p
-- request input device list with callback
input.getDevices( didGetDevices )
end
local buttonCallback = function(butState)
state.use_pitch = butState
end
local didGetDevices = function(devices)
-- FIXME: loop over devices here until we find one with btn_south
-- here just assume there's only one device
gamepad = devices[1]
if gamepad.hasCode(butCode) then
gamepad.setCallback(butCode, buttonCallback)
else
print("warning: connected device doesn't appear to be a gamepad")
end
end
-- load the desired engine with our callback
engine.load('test-sine', didLoadEngine)
cool, that's good to hear
currently i'm rewriting much of the lua with a clearer understanding of both the language and our needs.
this is what i probably should have done in the first place: write the script first against nonexistent API, then work backwards.
currently i have something like this for a stupid demo. it should do the following:
of particular note is the use of functions as first-class objects. anytime a function call should logically execute an action asynchronously on completion (e.g. engine load, input device query), that function should just accept a callback parameter.
local norns = require 'norns'
local engine = require 'engine'
local poll = require 'poll'
local input = require 'input'
local state = {
use_pitch = false,
pitch_poll = nil
gamepad = nil
}
local butCode = 'BTN_SOUTH'
local buttonCallback = function(butState)
state.use_pitch = butState
end
local hzCallback = function(hz)
if state.use_pitch then engine.hz(hz) end
end
local didLoadEngine = function(commands)
local p = poll.findByLabel('pitch_l')
if p then
-- start the poll with callback and period
p:start(hzCallback, 0.25)
-- store the poll in our global state so we can stop it later
state.pitch_poll = p
else
print("warning: couldn't find requested poll label")
end
-- request input device list with callback
input.getDevices( didGetDevices )
end
local didGetDevices = function(devices)
-- FIXME: loop over devices here until we find one with btn_south
-- here just assume there's only one device
gamepad = devices[1]
if gamepad.hasCode(butCode) then
gamepad.setCallback(butCode, buttonCallback)
else
print("warning: connected device doesn't appear to be a gamepad")
end
end
-- load the desired engine with our callback
engine.load('test-sine', didLoadEngine)
-- register cleanup function
norns.deinit = function()
state.pitch_poll.stop
state.gamepad.unsetCallback(butCode)
state = nil
butCode = nil
end
i'm going to close this as the current modules seem fine and we can revisit in the future if needed.
still getting to grips with lua style, so ATM it is all over the place.
i'd like to adopt a consistent and intuitive OOP style that wraps all C functions in class methods. for example, the Grid API is pretty clean; user gets a Grid object and uses methods on it, never having to know about the global
grid_refresh
, et al: https://github.com/catfact/norns/blob/dev/lua/sys/monome.lua#L18i'd like to extend this approach to Engine, Poll, Timer, Input, and whatever else we end up adding.
i think it's ok for
norns
to remain a singleton, manager-pattern class (global table) which is responsible for serving up these API objects based on the actual system resources.