miduo_client/Assets/ManagedResources/~Lua/eventlib.lua

286 lines
8.4 KiB
Lua

-- EventLib - An event library in pure lua (uses standard coroutine library)
-- License: WTFPL
-- Author: Elijah Frederickson
-- Version: 1.0
-- Copyright (C) 2012 LoDC
--
-- Description:
-- ROBLOX has an event library in its RbxUtility library, but it isn't pure Lua.
-- It originally used a BoolValue but now uses BindableEvent. I wanted to write
-- it in pure Lua, so here it is. It also contains some new features.
--
--
-- API:
--
-- EventLib
-- new([name])
-- aliases: CreateEvent
-- returns: the event, with a metatable __index for the EventLib table
-- Connect(event, func)
-- aliases: connect
-- returns: a Connection
-- Disconnect(event, [func])
-- aliases: disconnect
-- returns: the index of [func]
-- notes: if [func] is nil, it removes all connections
-- DisconnectAll(event)
-- notes: calls Disconnect()
-- Fire(event, ... <args>)
-- aliases: Simulate, fire
-- notes: resumes all :wait() first
-- Wait(event)
-- aliases: wait
-- returns: the Fire() arguments
-- notes: blocks the thread until Fire() is called
-- ConnectionCount(event)
-- returns: the number of current connections
-- Spawn(func)
-- aliases: spawn
-- returns: the result of func
-- notes: runs func in a separate coroutine/thread
-- Destroy(event)
-- aliases: destroy, Remove, remove
-- notes: renders the event completely useless
-- WaitForCompletion(event)
-- notes: blocks current thread until the current event Fire() is done
-- If a connected function calls WaitForCompletion, it will hang forever
-- IsWaiting(event)
-- returns: if the event has any waiters
-- WaitForWaiters(event)
-- notes: waits for all waiters to finish. Called in Fire() to make sure that all
-- the waiting threads are done before settings self.args to nil
--
-- Event
-- [All EventLib functions]
-- EventName
-- Property, defaults to "<Unknown Event>"
-- <Private fields>
-- handlers, waiter, args, waiters, executing,
--
-- Connection
-- Disconnect
-- aliases: disconnect
-- returns: the result of [Event].Disconnect
--
-- Basic usage (there are some tests on the bottom):
-- local EventLib = require'EventLib'
-- For ROBLOX use: repeat wait() until _G.EventLib local EventLib = _G.EventLib
--
-- local event = EventLib:new()
-- local con = event:Connect(function(...) print(...) end)
-- event:Fire("test") --> 'test' is print'ed
-- con:disconnect()
-- event:Fire("test") --> nothing happens: no connections
--
-- Supported versions/implementations of Lua:
-- Lua 5.1, 5.2
-- SharpLua 2
-- MetaLua
-- RbxLua (automatically registers if it detects ROBLOX)
--[[
Issues:
- None, but see [Todo 1]
Todo:
- fix Wait() for non-roblox clients without a wait function...
Changelog:
v1.0
- Initial version
]]
local _M = { }
_M._VERSION = "1.0"
_M._M = _M
_M._AUTHOR = "Elijah Frederickson"
_M._COPYRIGHT = "Copyright (C) 2012 LoDC"
local function spawn(f)
return coroutine.resume(coroutine.create(function()
f()
end))
end
_M.Spawn = spawn
_M.spawn = spawn
function _M:new(name)
assert(self ~= nil and type(self) == "table" and self == _M, "Invalid EventLib table (make sure you're using ':' not '.')")
local s = { }
s.handlers = { }
s.waiter = false
s.args = nil
s.waiters = 0
s.EventName = name or "<Unknown Event>"
s.executing = false
return setmetatable(s, { __index = self })
end
_M.CreateEvent = _M.new
function _M:Connect(handler)
assert(self ~= nil and type(self) == "table", "Invalid Event (make sure you're using ':' not '.')")
assert(type(handler) == "function", "Invalid handler. Expected function got " .. type(handler))
assert(self.handlers, "Invalid Event")
table.insert(self.handlers, handler)
local t = { }
t.Disconnect = function()
return self:Disconnect(handler)
end
t.disconnect = t.Disconnect
return t
end
_M.connect = _M.Connect
function _M:Disconnect(handler)
assert(self ~= nil and type(self) == "table", "Invalid Event (make sure you're using ':' not '.')")
assert(type(handler) == "function" or type(handler) == "nil", "Invalid handler. Expected function or nil, got " .. type(handler))
if not handler then
self.handlers = { }
else
for k, v in pairs(self.handlers) do
if v == handler then
self.handlers[k] = nil
return k
end
end
end
end
_M.disconnect = _M.Disconnect
function _M:DisconnectAll()
assert(self ~= nil and type(self) == "table", "Invalid Event (make sure you're using ':' not '.')")
self:Disconnect()
end
function _M:Fire(...)
assert(self ~= nil and type(self) == "table", "Invalid Event (make sure you're using ':' not '.')")
self.args = { ... }
self.executing = true
--[[
if self.waiter then
self.waiter = false
for k, v in pairs(self.waiters) do
coroutine.resume(v)
end
end]]
self.waiter = false
local i = 0
assert(self.handlers, "no handler table")
for k, v in pairs(self.handlers) do
i = i + 1
spawn(function()
v(unpack(self.args))
i = i - 1
if i == 0 then self.executing = false end
end)
end
self:WaitForWaiters()
self.args = nil
--self.executing = false
end
_M.Simulate = _M.Fire
_M.fire = _M.Fire
function _M:Wait()
assert(self ~= nil and type(self) == "table", "Invalid Event (make sure you're using ':' not '.')")
self.waiter = true
self.waiters = self.waiters + 1
--[[
local c = coroutine.create(function()
coroutine.yield()
return unpack(self.args)
end)
table.insert(self.waiters, c)
coroutine.resume(c)
]]
while self.waiter or not self.args do if wait then wait() end end
self.waiters = self.waiters - 1
return unpack(self.args)
end
_M.wait = _M.Wait
function _M:ConnectionCount()
assert(self ~= nil and type(self) == "table", "Invalid Event (make sure you're using ':' not '.')")
return #self.handlers
end
function _M:Destroy()
assert(self ~= nil and type(self) == "table", "Invalid Event (make sure you're using ':' not '.')")
self:DisconnectAll()
for k, v in pairs(self) do
self[k] = nil
end
setmetatable(self, { })
end
_M.destroy = _M.Destroy
_M.Remove = _M.Destroy
_M.remove = _M.Destroy
function _M:WaitForCompletion()
assert(self ~= nil and type(self) == "table", "Invalid Event (make sure you're using ':' not '.')")
while self.executing do if wait then wait() end end
while self.waiters > 0 do if wait then wait() end end
end
function _M:IsWaiting()
assert(self ~= nil and type(self) == "table", "Invalid Event (make sure you're using ':' not '.')")
return self.waiter or self.waiters > 0
end
function _M:WaitForWaiters()
assert(self ~= nil and type(self) == "table", "Invalid Event (make sure you're using ':' not '.')")
while self.waiters > 0 do if wait then wait() end end
end
-- Tests
if false then
local e = _M:new("test")
local f = function(...) print("| Fired!", ...) end
local e2 = e:connect(f)
e:fire("arg1", 5, { })
-- Would work in a ROBLOX Script, but not on Lua 5.1...
if script ~= nil and failhorribly then
spawn(function() print("Wait() results", e:wait()) print"|- done waiting!" end)
end
e:fire(nil, "x")
print("Disconnected events index:", e:disconnect(f))
print("Couldn't disconnect an already disconnected handler?", e2:disconnect()==nil)
print("Connections:", e:ConnectionCount())
assert(e:ConnectionCount() == 0 and e:ConnectionCount() == #e.handlers)
e:connect(f)
e:connect(function() print"Throwing error... " error("...") end)
e:fire("Testing throwing an error...")
e:disconnect()
e:Simulate()
f("plain function call")
assert(e:ConnectionCount() == 0)
if wait then
e:connect(function() wait(2, true) print'fired after waiting' end)
e:Fire()
e:WaitForCompletion()
print'Done!'
end
local failhorribly = false
if failhorribly then -- causes an eternal loop in the WaitForCompletion call
e:connect(function() e:WaitForCompletion() print'done with connected function' end)
e:Fire()
print'done'
end
e:Destroy()
assert(not e.EventName and not e.Fire and not e.Connect)
end
if shared and Instance then -- ROBLOX support
shared.EventLib = _M
_G.EventLib = _M
end
return _M