local Luvent = {} Luvent.__index = Luvent function Luvent.newEvent() local event = {} event.actions = {} return setmetatable(event, Luvent) end function Luvent.__eq(e1, e2) if getmetatable(e1) ~= Luvent or getmetatable(e2) ~= Luvent then return false end if #e1.actions ~= #e2.actions then return false end for _, a1 in ipairs(e1.actions) do local found = false for _, a2 in ipairs(e2.actions) do if a1 == a2 then found = true break end end if found == false then return false end end return true end Luvent.Action = {} Luvent.Action.__index = Luvent.Action local isActionCallable = function(callable) if type(callable) == "table" then if type(getmetatable(callable).__call) == "function" then return true end elseif type(callable) == "function" then return true elseif type(callable) == "thread" and coroutine.status(callable) ~= "dead" then return true end return false end local newAction = function(callable) local action = {} assert(isActionCallable(callable)) action.callable = callable action.id = tostring(callable) action.enabled = true action.priority = 0 action.limit = -1 action.numberOfInvocations = 0 action.interval = 0 action.timeOfLastInvocation = os.time() return setmetatable(action, Luvent.Action) end function Luvent.Action.__eq(a1, a2) if getmetatable(a1) ~= Luvent.Action or getmetatable(a2) ~= Luvent.Action then return false end return a1.id == a2.id end local findAction = function(event, actionToFind) local key if type(actionToFind) == "string" then key = "id" elseif isActionCallable(actionToFind) then key = "callable" else error("Invalid action parameter: " .. tostring(actionToFind)) end for index, action in ipairs(event.actions) do if action[key] == actionToFind then return true, index end end return false, nil end local sortActionsByPriority = function(event) table.sort(event.actions, function(a1, a2) return a1.priority > a2.priority end) end function Luvent:addAction(actionToAdd) assert(isActionCallable(actionToAdd) == true) if self:hasAction(actionToAdd) then return end local new = newAction(actionToAdd) table.insert(self.actions, new) return new.id end function Luvent:removeAction(actionToRemove) local exists, index = findAction(self, actionToRemove) if exists == true then table.remove(self.actions, index) end end function Luvent:removeAllActions() self.actions = {} end function Luvent:getActionCount() return #self.actions end function Luvent:hasAction(actionToFind) return (findAction(self, actionToFind)) end local invokeAction = function(action, ...) if action.enabled == false then return true end if type(action.callable) == "thread" then coroutine.resume(action.callable, ...) if coroutine.status(action.callable) == "dead" then return false end else action.callable(...) end action.numberOfInvocations = action.numberOfInvocations + 1 if action.limit >= 0 and action.numberOfInvocations >= action.limit then action.enabled = false end return true end function Luvent:trigger(...) local call = function(action, ...) local keep = invokeAction(action, ...) if keep == false then self:removeAction(action.id) end end sortActionsByPriority(self) for _, action in ipairs(self.actions) do if action.interval > 0 then if os.difftime(os.time(), action.timeOfLastInvocation) >= action.interval then call(action, ...) action.timeOfLastInvocation = os.time() end else call(action, ...) end end end local createActionSetter = function(property, valueType, default) return function(event, actionToFind, newValue) local propertyValue = newValue or default local propertyValueType = type(propertyValue) local exists, index = findAction(event, actionToFind) assert(exists) assert(propertyValueType == valueType) if propertyValueType == "number" then assert(0 <= propertyValue) end event.actions[index][property] = propertyValue end end local createActionGetter = function(property) return function(event, action) local exists, index = findAction(event, action) assert(exists) assert(event.actions[index][property]) return event.actions[index][property] end end Luvent.setActionInterval = createActionSetter("interval", "number") Luvent.getActionInterval = createActionGetter("interval") Luvent.removeActionInterval = createActionSetter("interval", "number", 0) Luvent.setActionPriority = createActionSetter("priority", "number") Luvent.getActionPriority = createActionGetter("priority") Luvent.removeActionPriority = createActionSetter("priority", "number", 0) Luvent.enableAction = createActionSetter("enabled", "boolean", true) Luvent.disableAction = createActionSetter("enabled", "boolean", false) function Luvent:isActionEnabled(actionToFind) local exists, index = findAction(self, actionToFind) assert(exists) return self.actions[index].enabled end function Luvent:setActionTriggerLimit(actionToFind, limit) local exists, index = findAction(self, actionToFind) assert(exists) assert(type(limit) == "number" and 0 <= limit) self.actions[index].limit = limit self.actions[index].numberOfInvocations = 0 if limit == 0 then self.actions[index].enabled = false end end function Luvent:getActionTriggerLimit(actionToFind) local exists, index = findAction(self, actionToFind) assert(exists) assert(self.actions[index].limit) return self.actions[index].limit end function Luvent:removeActionTriggerLimit(actionToFind) local exists, index = findAction(self, actionToFind) assert(exists) self.actions[index].limit = -1 self.actions[index].numberOfInvocations = 0 self.actions[index].enabled = true end function Luvent:allActions() local index = 0 return function() index = index + 1 local action = self.actions[index] if action then return action.id end end end function Luvent:forEachAction(f) for action in self:allActions() do f(self, action) end end return Luvent