lk.Properties

:__index (idx)

local lib = {type='lk.Properties'} lk.Properties = lib

-- node.property('foo', 'info message') local function node_declare_property(node, prop_name, info) local self = node.prop local prop = self.properties[prop_name] if not prop then -- declaring a new property (not an update) prop = {name = prop_name, observers = {}} self.properties[prop_name] = prop end -- only thing that an be updated prop.info = info end

-- property accessor with callbacks local function set_property(self, idx, value) local prop = self.properties[idx] if not prop then error(string.format('Unknown property %s', idx)) end local old_value = self.values[idx] local callback = self.node[prop.name] if callback then value = callback(self.node, value) end if value ~= old_value then -- set new value self.values[idx] = value -- notify value change local observers = prop.observers for _, list in ipairs(observers) do -- list[1] == method -- list[2] == target -- list[3] == target_idx -- set remote property -- target:method(target_idx, value) list1 end end end lib.__newindex = set_property

local function add_observer(self, prop_name, target_method, target, target_idx) local prop = self.properties[prop_name] if not prop then error(string.format("Cannot connect '%s' to '%s' (source property does not exist)", prop_name, target_idx)) end local observers = prop.observers -- make sure we do not connect twice for _, observer in ipairs(observers) do if observer[1] == target_method and observer[2] == target and observer[3] == target_idx then return end end -- add observer table.insert(observers, {target_method, target, target_idx}) end lib.add_observer = add_observer

local function node_connect_observer(node, prop_name, arg2, arg3, arg4) if not arg4 then -- arg2 == node -- arg3 == target_idx add_observer(node.prop, prop_name, set_property, arg2.prop, arg3) else -- arg2 == target_method -- arg3 == target -- arg4 == target_idx add_observer(node.prop, prop_name, arg2, arg3, arg4) end end

local function remove_observer(self, prop_name, target_method, target, target_idx) local prop = self.properties[prop_name] if not prop then -- ignore return end local observers = prop.observers

-- remove connected observer local i = observers while i > 0 do local observer = observers[i] if observer[1] == target_method and observer[2] == target and (target_idx == nil or observer[3] == target_idx) then -- remove element i from table table.remove(observers, i) end i = i - 1 end end lib.remove_observer = remove_observer

local function node_disconnect_observer(node, arg1, arg2, arg3) if arg2 == nil then -- arg1 == target -- disconnect observer from all properties for prop_name, _ in pairs(node.prop.properties) do remove_observer(node.prop, prop_name, set_property, arg1.prop) end elseif arg3 == nil then -- arg1 = prop_name -- arg2 = target node -- disconnect observer from property 'prop_name' remove_observer(node.prop, arg1, set_property, arg2.prop) else -- arg1 = prop_name -- arg2 = target -- arg3 = target_idx -- disconnect observer hook 'target_idx' from property 'prop_name' remove_observer(node.prop, arg1, set_property, arg2.prop, arg3) end end

setmetatable(lib, { -- new method __call = function(lib, node) local instance = {properties = {}, node = node, values = {}} -- declare property methods -- FIXME: move these into lk.Node node.property = node_declare_property node.connect = node_connect_observer node.disconnect = node_disconnect_observer node.prop = instance setmetatable(instance, lib) return instance end})