Skip to content

markstinson/Luvent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Luvent: A Simple Event Library for Lua

Luvent is in early development and not recommended for any production software. The API is subject to change and break compatibility without warning.

Luvent is a library for Lua, written entirely in Lua, which helps support event-driven programming. Luvent lets you create events, which are objects with any number of associated functions. Whenever you trigger an event the library will execute all functions attached to that event. You may trigger an event multiple times and can provide different arguments to that event’s functions each time.

Requirements

Luvent requires one of the following Lua implementations:

These are the versions we use to test Luvent. It should work with later versions of each, and possibly older versions as well.

Optional

The following programs are not necessary in order to use Luvent but you will need them to run the unit tests, generate API documentation, and so on.

Installation

All you need to do is place src/Luvent.lua in a directory that is part of package.path so that Lua can find and load it. If you have Busted then you should run make tests first to ensure that Luvent behaves as intended.

Documentation

Running the command make docs will populate the docs/ directory with HTML documents that describe Luvent’s API. The public API consists of the following functions and methods:

  • Luvent.newEvent()
  • Luvent:addAction(action)
  • Luvent:setActionInterval(action_or_id, interval)
  • Luvent:removeAction(action_or_id)
  • Luvent:getActionCount()
  • Luvent:callsAction(action_or_id)
  • Luvent:trigger(...)

Note: Developers must never rely on the properties of the return values of Luvent:newEvent(). Its non-method properties are not part of the public API.

The parameter action must be a function, coroutine, or table that implements the __call() metamethod. The addAction() method returns an ID for the new action which you can save and later pass on to any method that accepts action_or_id. This is useful for keeping track of actions when you have no access to the original action itself, e.g. adding an anonymous function as an action.

Below is a lengthy example that demonstrates the basics of creating and triggering events, and adding and removing actions.

-- In this example we will pretend we are implementing a module in a
-- game that creates and manages enemies.  To simplfy the example we
-- use Enrique García Cota's terrific MiddleClass library in order
-- to make the class and objects for enemies.
--
--     https://github.com/kikito/middleclass
--
require "middleclass"

local Luvent = require "Luvent"
local Enemy = class("Enemy")

-- This hash contains a reference to all living enemies.
Enemy.static.LIVING = {}

function Enemy:initialize(family, maxHP)
    self.family = family
    self.maxHP = maxHP
    self.HP = maxHP
    table.insert(Enemy.LIVING, self)
end

-- This is the event we trigger any time an enemy dies.
Enemy.static.onDie = Luvent.newEvent()

-- This method applies damage to an enemy and will trigger its 'onDie'
-- event if the enemy's hit points reach zero or less.
function Enemy:damage(damage)
    self.HP = self.HP - damage
    if self.HP <= 0 then
        Enemy.onDie:trigger(self)
    end
end

-- Now we can start associating actions with the 'onDie' event.  First
-- we start by removing the enemy from the table of living enemies.
Enemy.onDie:addAction(
    function (enemy)
        for index,living_enemy in ipairs(Enemy.LIVING) do
            if enemy == living_enemy then
                table.remove(Enemy.LIVING, index)
                return
            end
        end
    end)

-- For debugging we want to see on the console when an enemy dies, so
-- we add that as a separate action.  This time we save the return
-- value of addAction() so that later we can use that to remove the
-- action when we want to stop printing debugging output.
local debugAction = Enemy.onDie:addAction(
    function (enemy)
        print(string.format("Enemy %s died", enemy.family))
    end)

-- Now we make some enemies and kill them to demonstrate how the
-- trigger() method used in Enemy:damage() invokes the actions.

local bee = Enemy:new("Bee", 10)
local ladybug = Enemy:new("Ladybug", 1)

-- This will print "2"
print(#Enemy.LIVING)

-- This kills the enemy so the program invokes the two actions above,
-- meaning it will print "Enemy Ladbug died" to the console and will
-- remove it from Enemy.LIVING.
ladybug:damage(100)
print(#Enemy.LIVING)

-- Now we turn off the debugging output by removing that action.  As a
-- result we will see no output after killing the bee.
Enemy.onDie:removeAction(debugAction)
bee:damage(50)
print(#Enemy.LIVING)

You can also use coroutines as actions. This allows you to do things that you could not with regular functions, such as make sure that an event will invoke an action a fixed number of times, e.g.:

-- In this example we are writing code that affects a player in a
-- hypothetical game.  Every time the player dies and uses a continue
-- we want to reset the player's properties.  However, the first time
-- the player uses a continue we want to save the current score, and
-- then we do not execute that action again because our game only
-- records scores the player achieves with the first continue.

require "middleclass"

local Luvent = require "Luvent"
local Player = class("Player")

Player.static.MAX_POWER_LEVEL = 10

function Player:initialize()
    self.HP = 100
    self.powerLevel = Player.MAX_POWER_LEVEL
    self.score = 0
end

function Player:continue()
    Player.onContinue:trigger(self)
end

Player.static.onContinue = Luvent.newEvent()

-- If we use a coroutine for an action then Luvent will automatically
-- remove the action when the coroutine is dead.  That means the
-- action below will only run once because the status of the coroutine
-- becomes dead after its first invocation.
Player.onContinue:addAction(
    coroutine.create(
        function (player)
            -- This is where we would write 'player.score' to a file.
        end))

-- This is the stuff we want to do every time the player continues.
Player.onContinue:addAction(
    function (player)
        player.HP = 100
        player.powerlevel = 1
        player.score = 0
    end)

local P1 = Player:new()

-- The first time we trigger this event it will execute both of the
-- actions above.
P1:continue()

-- But now any other time we trigger the event it will not run the
-- action that saves the score.
P1:continue()

Luvent discards all return values from action functions or anything that coroutines yield.

Acknowledgments and Alternatives

EventLib by Elijah Frederickson is the major inspiration for the design and implementation of Luvent. The Luvent API also owes a debt of ideas and names to Node.js by Ryan Dahl et al. The following is a list of alternatives to Luvent for the sake of comparison, as some may be better suited for some developers or projects:

License

The MIT License

Copyright 2013 Eric James Michael Ritz

About

Simple Event Library for Lua

Resources

License

Stars

Watchers

Forks

Packages

No packages published