diff --git a/README.md b/README.md index 9d43eb2d..4689dcbf 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # CanaryEngine -CanaryEngine is a lightweight and performant game framework for beginners and power-users alike. View more info at [CanaryEngine | Docs](https://canary-development.github.io/CanaryEngine) \ No newline at end of file +CanaryEngine is a lightweight and performant game framework for beginners and power-users alike. View more info at the [CanaryEngine documentation](https://canary-development.github.io/CanaryEngine) \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md index 754888a9..102af7e7 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,7 +6,9 @@ keywords: [roblox, game, framework, changelog, updates] # Changelog -## 4.0.0 +This is the changelog which is changed every update, and we follow semver. To see what we're working on, head to our [Trello](https://trello.com/b/fTJOmMua/canaryengine) board. + +## 4.0.0-rc1 ### Added diff --git a/docs/index.md b/docs/index.md index c30833ef..936109ce 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,16 +8,10 @@ hero: actions: - theme: brand text: Documentation - link: /tutorials/update + link: /tutorial/update - theme: alt text: API Reference link: /api/engine/framework/canaryengine - - theme: alt - text: Trello Board - link: https://trello.com/b/fTJOmMua/canaryengine - - theme: alt - text: Roblox Plugin - link: https://create.roblox.com/marketplace/asset/12591143042 features: - icon: 🎒 diff --git a/plugin/settings/settings.json b/plugin/settings/settings.json index f6b1796a..02d34a30 100644 --- a/plugin/settings/settings.json +++ b/plugin/settings/settings.json @@ -1,4 +1,5 @@ { "Files": 14, - "CanaryStudioChangelog": "We've made some large changes since the last update to the plugin and have fixed lots of bugs!\n\nInstalling has been revamped - The process is now a lot quicker and more performant\nRedesigned Home Page - The home page now has a menu bar and shows this changelog\nInternal code rework - It's now a lot easier to fix issues and add new features" + "FrameworkName": ["Framework", "CanaryEngineFramework"], + "CanaryStudioChangelog": "Need help? You can refer to our help articles at https://canary-development.github.io/CanaryEngine.\nFrom there, you can view our documentation on libraries the plugin itself." } \ No newline at end of file diff --git a/plugin/src/json/librarieslist.json b/plugin/src/json/librarieslist.json index 634e0840..08f02409 100644 --- a/plugin/src/json/librarieslist.json +++ b/plugin/src/json/librarieslist.json @@ -69,10 +69,10 @@ "Source": "https://raw.githubusercontent.com/red-blox/Util/main/libs/Spawn/Spawn.luau" } }, - "Promise": { + "Future": { "$className": "ModuleScript", "$properties": { - "Source": "https://raw.githubusercontent.com/red-blox/Util/main/libs/Promise/Promise.luau" + "Source": "https://raw.githubusercontent.com/red-blox/Util/main/libs/Future/Future.luau" } }, "Init": { @@ -97,13 +97,19 @@ "Signal": { "$className": "ModuleScript", "$properties": { - "Source": "https://raw.githubusercontent.com/canary-development/CanaryEngine/master/src/lua/libraries/benchmark/Vendor/Signal.luau" + "Source": "https://raw.githubusercontent.com/canary-development/CanaryEngine/master/src/lua/libraries/signal/init.luau" + } + }, + "Spawn": { + "$className": "ModuleScript", + "$properties": { + "Source": "https://raw.githubusercontent.com/canary-development/CanaryEngine/master/src/lua/libraries/signal/spawn.luau" } }, "Statistics": { "$className": "ModuleScript", "$properties": { - "Source": "https://raw.githubusercontent.com/canary-development/CanaryEngine/master/src/lua/libraries/benchmark/Vendor/Statistics.luau" + "Source": "https://raw.githubusercontent.com/canary-development/CanaryEngine/master/src/lua/libraries/statistics/init.luau" } } }, diff --git a/plugin/src/lua/templates/scripts/client-script.lua b/plugin/src/lua/templates/scripts/client-script.lua index 6e3e6e99..0a622fd1 100644 --- a/plugin/src/lua/templates/scripts/client-script.lua +++ b/plugin/src/lua/templates/scripts/client-script.lua @@ -1,6 +1,7 @@ -- // Engine local ReplicatedStorage = game:GetService("ReplicatedStorage") + local CanaryEngine = require(ReplicatedStorage.Framework.Init) local EngineClient = CanaryEngine.GetEngineClient() @@ -9,8 +10,6 @@ local Media = ReplicatedStorage.EngineClient.Media -- // Variables -local variable = 1 - -- // Functions -- // Connections diff --git a/plugin/src/lua/templates/scripts/server-script.lua b/plugin/src/lua/templates/scripts/server-script.lua index 080bd945..fc550cef 100644 --- a/plugin/src/lua/templates/scripts/server-script.lua +++ b/plugin/src/lua/templates/scripts/server-script.lua @@ -1,7 +1,8 @@ --- // Engine +-- // Script local ReplicatedStorage = game:GetService("ReplicatedStorage") local ServerStorage = game:GetService("ServerStorage") + local CanaryEngine = require(ReplicatedStorage.Framework.Init) local EngineServer = CanaryEngine.GetEngineServer() @@ -10,8 +11,6 @@ local Media = ServerStorage.EngineServer.Media -- // Variables -local variable = 1 - -- // Functions -- // Connections diff --git a/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/Vendor/RedEvent.luau b/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/Vendor/RedEvent.luau index 709055dc..55e6b383 100644 --- a/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/Vendor/RedEvent.luau +++ b/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/Vendor/RedEvent.luau @@ -1,4 +1,4 @@ -local Red = require(script.Parent.Red) +local Red = require(script.Parent.Parent.Vendor.Red) return Red.Event(function() return diff --git a/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/init.luau b/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/init.luau index a238e32d..05ce629a 100644 --- a/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/init.luau +++ b/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/init.luau @@ -30,7 +30,7 @@ function NetworkController.CreateController(name: string) local self = setmetatable({ }, RunService:IsClient() and Indexes[1] or Indexes[2]) self.Name = name - self._Bridge = BridgeNet.ReferenceBridge(name) + self._Event = BridgeNet.ReferenceBridge(name) return self end @@ -38,18 +38,16 @@ end -- Network Controller Client function NetworkControllerClient:Fire(data: ({any} | any)?) - self._Bridge:Fire(Sanitize(data)) + self._Event:Fire(Sanitize(data)) end function NetworkControllerClient:Listen(func: (data: {any}?) -> ()) - local Connection = self._Bridge:Connect(func) - - return Connection + self._Event:Connect(func) end function NetworkControllerClient:InvokeAsync(data: ({any} | any)?) return Future.new(function() - return Sanitize(self._Bridge:InvokeServerAsync(Sanitize(data))) + return Sanitize(self._Event:InvokeServerAsync(Sanitize(data))) end) end @@ -57,24 +55,24 @@ end function NetworkControllerServer:Fire(recipients: Player | {Player}, data: ({any} | any)?) if type(recipients) ~= "table" then - self._Bridge:Fire(recipients, Sanitize(data)) + self._Event:Fire(recipients, Sanitize(data)) return end - self._Bridge:Fire(BridgeNet.Players(recipients), Sanitize(data)) + self._Event:Fire(BridgeNet.Players(recipients), Sanitize(data)) end function NetworkControllerServer:FireAll(data: ({any} | any)?) - self._Bridge:Fire(BridgeNet.AllPlayers(), Sanitize(data)) + self._Event:Fire(BridgeNet.AllPlayers(), Sanitize(data)) end function NetworkControllerServer:FireExcept(except: Player | {Player}, data: ({any} | any)?) if type(except) ~= "table" then - self._Bridge:Fire(BridgeNet.PlayersExcept({except}), Sanitize(data)) + self._Event:Fire(BridgeNet.PlayersExcept({except}), Sanitize(data)) return end - self._Bridge:Fire(BridgeNet.PlayersExcept(except), Sanitize(data)) + self._Event:Fire(BridgeNet.PlayersExcept(except), Sanitize(data)) end function NetworkControllerServer:FireInRange(comparePoint: Vector3, maximumRange: number, data: ({any} | any)?) @@ -90,8 +88,7 @@ function NetworkControllerServer:FireInRange(comparePoint: Vector3, maximumRange end function NetworkControllerServer:Listen(func: (sender: Player, data: {any}?) -> ()) - local Connection = self._Bridge:Connect(func) - return Connection + self._Event:Connect(func) end function NetworkControllerServer:SetRateLimit(maxInvokesPerSecond: number, invokeOverflowCallback: ((sender: Player) -> ())?) @@ -104,13 +101,16 @@ function NetworkControllerServer:SetRateLimit(maxInvokesPerSecond: number, invok return end - self._Bridge:RateLimit(maxInvokesPerSecond, invokeOverflowCallback) + self._Event:RateLimit(maxInvokesPerSecond, invokeOverflowCallback) end function NetworkControllerServer:OnInvoke(callback: (sender: Player, data: {any}?) -> (({any} | any))) - self._Bridge.OnServerInvoke = callback + self._Event.OnServerInvoke = callback end -- // Actions -return NetworkController \ No newline at end of file +table.freeze(NetworkControllerServer) +table.freeze(NetworkControllerClient) + +return table.freeze(NetworkController) \ No newline at end of file diff --git a/src/lua/framework/Vendor/Controllers/Vendor/SignalController/init.luau b/src/lua/framework/Vendor/Controllers/Vendor/SignalController/init.luau index 3e1ab3e1..58f7c7e6 100644 --- a/src/lua/framework/Vendor/Controllers/Vendor/SignalController/init.luau +++ b/src/lua/framework/Vendor/Controllers/Vendor/SignalController/init.luau @@ -1,9 +1,7 @@ -- // Package local SignalController = { } -local SignalControllerObject = { } - -SignalController.__index = SignalControllerObject +SignalController.__index = SignalController -- // Variables @@ -24,15 +22,15 @@ function SignalController.CreateController(name: string) return self end -function SignalControllerObject:Fire(data: ({any} | any)?) +function SignalController:Fire(data: ({any} | any)?) self._Signal:Fire(Sanitize(data)) end -function SignalControllerObject:Connect(func: (data: {any}?) -> ()) +function SignalController:Connect(func: (data: {any}?) -> ()) return self._Signal:Connect(func) end -function SignalControllerObject:Once(func: (data: {any}?) -> ()) +function SignalController:Once(func: (data: {any}?) -> ()) local Disconnect Disconnect = self._Signal:Connect(function(data) @@ -43,11 +41,11 @@ function SignalControllerObject:Once(func: (data: {any}?) -> ()) return Disconnect end -function SignalControllerObject:Wait(): {any}? +function SignalController:Wait(): {any}? return self._Signal:Wait() end -function SignalControllerObject:DisconnectAll() +function SignalController:DisconnectAll() self._Signal:DisconnectAll() end @@ -55,4 +53,4 @@ end -- // Actions -return SignalController \ No newline at end of file +return table.freeze(SignalController) \ No newline at end of file diff --git a/src/lua/framework/Vendor/Debugger.luau b/src/lua/framework/Vendor/Debugger.luau index ff24ec23..8942d1d3 100644 --- a/src/lua/framework/Vendor/Debugger.luau +++ b/src/lua/framework/Vendor/Debugger.luau @@ -175,4 +175,4 @@ end -- // Actions -return EngineDebugger \ No newline at end of file +return table.freeze(EngineDebugger) \ No newline at end of file diff --git a/src/lua/framework/Vendor/Libraries/RedbloxUtils/Signal.luau b/src/lua/framework/Vendor/Libraries/RedbloxUtils/Signal.luau index acb9e031..fb3e7a05 100644 --- a/src/lua/framework/Vendor/Libraries/RedbloxUtils/Signal.luau +++ b/src/lua/framework/Vendor/Libraries/RedbloxUtils/Signal.luau @@ -1,6 +1,3 @@ --- // by jackdotink --- // modded by lolmansReturn - local Spawn = require(script.Parent.Spawn) type SignalNode = { diff --git a/src/lua/framework/Vendor/Runtime.luau b/src/lua/framework/Vendor/Runtime.luau index 18d5e25b..202dd895 100644 --- a/src/lua/framework/Vendor/Runtime.luau +++ b/src/lua/framework/Vendor/Runtime.luau @@ -27,4 +27,4 @@ local EngineRuntimeSettings = { EngineRuntime.Context = EngineRuntimeContext EngineRuntime.Settings = EngineRuntimeSettings -return EngineRuntime \ No newline at end of file +return table.freeze(EngineRuntime) \ No newline at end of file diff --git a/src/lua/framework/init.luau b/src/lua/framework/init.luau index adde4b7f..47776b61 100644 --- a/src/lua/framework/init.luau +++ b/src/lua/framework/init.luau @@ -56,7 +56,7 @@ end function CanaryEngine.GetEngineServer() if RuntimeContext.Server then CanaryEngineServer.Data = require(EngineVendor.Libraries.EasyProfile.Init) - return CanaryEngineServer + return table.freeze(CanaryEngineServer) else Debugger.Debug(error, "Failed to fetch 'EngineServer', context must be server.", nil, false) return @@ -71,7 +71,7 @@ function CanaryEngine.GetEngineClient() CanaryEngineClient.PlayerGui = Player:WaitForChild("PlayerGui") :: typeof(game:GetService("StarterGui")) CanaryEngineClient.PlayerBackpack = Player:WaitForChild("Backpack") :: typeof(game:GetService("StarterPack")) - return CanaryEngineClient + return table.freeze(CanaryEngineClient) else Debugger.Debug(error, "Failed to fetch 'EngineClient', context must be client.", nil, false) return @@ -80,7 +80,7 @@ end -- Exclusive API for replicated will come soon function CanaryEngine.GetEngineReplicated() - return CanaryEngineReplicated + return table.freeze(CanaryEngineReplicated) end -- Signal Creation diff --git a/src/lua/libraries/benchmark/Vendor/Signal.luau b/src/lua/libraries/benchmark/Vendor/Signal.luau deleted file mode 100644 index 6ad88b13..00000000 --- a/src/lua/libraries/benchmark/Vendor/Signal.luau +++ /dev/null @@ -1,181 +0,0 @@ --------------------------------------------------------------------------------- --- Batched Yield-Safe Signal Implementation -- --- This is a Signal class which has effectively identical behavior to a -- --- normal RBXScriptSignal, with the only difference being a couple extra -- --- stack frames at the bottom of the stack trace when an error is thrown. -- --- This implementation caches runner coroutines, so the ability to yield in -- --- the signal handlers comes at minimal extra cost over a naive signal -- --- implementation that either always or never spawns a thread. -- --- -- --- API: -- --- local Signal = require(THIS MODULE) -- --- local sig = Signal.new() -- --- local connection = sig:Connect(function(arg1, arg2, ...) ... end) -- --- sig:Fire(arg1, arg2, ...) -- --- connection:Disconnect() -- --- sig:DisconnectAll() -- --- local arg1, arg2, ... = sig:Wait() -- --- -- --- Licence: -- --- Licenced under the MIT licence. -- --- -- --- Authors: -- --- stravant - July 31st, 2021 - Created the file. -- --- lolmansReturn - August 3rd, 2023 - Edited the file. -- --------------------------------------------------------------------------------- - --- The currently idle thread to run the next handler on -local freeRunnerThread = nil - --- Function which acquires the currently idle handler runner thread, runs the --- function fn on it, and then releases the thread, returning it to being the --- currently idle one. --- If there was a currently idle runner thread already, that's okay, that old --- one will just get thrown and eventually GCed. -local function acquireRunnerThreadAndCallEventHandler(fn, ...) - local acquiredRunnerThread = freeRunnerThread - freeRunnerThread = nil - fn(...) - -- The handler finished running, this runner thread is free again. - freeRunnerThread = acquiredRunnerThread -end - --- Coroutine runner that we create coroutines of. The coroutine can be --- repeatedly resumed with functions to run followed by the argument to run --- them with. -local function runEventHandlerInFreeThread() - -- Note: We cannot use the initial set of arguments passed to - -- runEventHandlerInFreeThread for a call to the handler, because those - -- arguments would stay on the stack for the duration of the thread's - -- existence, temporarily leaking references. Without access to raw bytecode - -- there's no way for us to clear the "..." references from the stack. - while true do - acquireRunnerThreadAndCallEventHandler(coroutine.yield()) - end -end - --- Connection class -local Connection = {} -Connection.__index = Connection - -function Connection.new(signal, fn) - return setmetatable({ - Connected = true, - _signal = signal, - _fn = fn, - _next = false, - }, Connection) -end - -function Connection:Disconnect() - self.Connected = false - - -- Unhook the node, but DON'T clear it. That way any fire calls that are - -- currently sitting on this node will be able to iterate forwards off of - -- it, but any subsequent fire calls will not hit it, and it will be GCed - -- when no more fire calls are sitting on it. - if self._signal._handlerListHead == self then - self._signal._handlerListHead = self._next - else - local prev = self._signal._handlerListHead - while prev and prev._next ~= self do - prev = prev._next - end - if prev then - prev._next = self._next - end - end -end - --- Make Connection strict -setmetatable(Connection, { - __index = function(tb, key) - error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2) - end, - __newindex = function(tb, key, value) - error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2) - end -}) - --- Signal class -local Signal = {} -Signal.__index = Signal - -function Signal.new() - return setmetatable({ - _handlerListHead = false, - }, Signal) -end - -function Signal:Connect(fn) - local connection = Connection.new(self, fn) - if self._handlerListHead then - connection._next = self._handlerListHead - self._handlerListHead = connection - else - self._handlerListHead = connection - end - return connection -end - --- Disconnect all handlers. Since we use a linked list it suffices to clear the --- reference to the head handler. -function Signal:DisconnectAll() - self._handlerListHead = false -end - --- Signal:Fire(...) implemented by running the handler functions on the --- coRunnerThread, and any time the resulting thread yielded without returning --- to us, that means that it yielded to the Roblox scheduler and has been taken --- over by Roblox scheduling, meaning we have to make a new coroutine runner. -function Signal:Fire(...) - local item = self._handlerListHead - while item do - if item.Connected then - if not freeRunnerThread then - freeRunnerThread = coroutine.create(runEventHandlerInFreeThread) - -- Get the freeRunnerThread to the first yield - coroutine.resume(freeRunnerThread) - end - task.spawn(freeRunnerThread, item._fn, ...) - end - item = item._next - end -end - --- Implement Signal:Wait() in terms of a temporary connection using --- a Signal:Connect() which disconnects itself. -function Signal:Wait() - local waitingCoroutine = coroutine.running() - local cn; - cn = self:Connect(function(...) - cn:Disconnect() - task.spawn(waitingCoroutine, ...) - end) - return coroutine.yield() -end - --- Implement Signal:Once() in terms of a connection which disconnects --- itself before running the handler. -function Signal:Once(fn) - local cn; - cn = self:Connect(function(...) - if cn.Connected then - cn:Disconnect() - end - fn(...) - end) - return cn -end - --- Make signal strict -setmetatable(Signal, { - __index = function(tb, key) - error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2) - end, - __newindex = function(tb, key, value) - error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2) - end -}) - -return Signal \ No newline at end of file diff --git a/src/lua/libraries/benchmark/Vendor/Statistics.luau b/src/lua/libraries/benchmark/Vendor/Statistics.luau deleted file mode 100644 index b0fd9f9a..00000000 --- a/src/lua/libraries/benchmark/Vendor/Statistics.luau +++ /dev/null @@ -1,95 +0,0 @@ ---[=[ - The statistics class. - - @class Statistics -]=] -local Statistics = { } -local Utility = require(script.Parent.Utility) - ---[=[ - Gets the number that is in the middle of the dataset, more info can be found [here](https://en.wikipedia.org/wiki/Median). Here's an example scenario: - - ```lua - local CollectedData = {6, 8, 3, 7, 9, 0, 4, 1} - - print(Statistics.GetMedian(CollectedData)) - -- Output: 8 - ``` - - @param numberList {number} -- The dataset to perform the action on. - - @return number -]=] -function Statistics.GetMedian(numberList: {number}): number - local TotalNumbers = #numberList - local IsTotalEven = TotalNumbers % 2 == 0 - - if not IsTotalEven then - return numberList[(TotalNumbers / 2) + 0.5] - end - - local FirstMedian = numberList[TotalNumbers / 2] - local SecondMedian = numberList[(TotalNumbers / 2) + 1] - - return (FirstMedian + SecondMedian) / 2 -end - ---[=[ - Gets the most common number in the dataset, more info can be found [here](https://en.wikipedia.org/wiki/Mean). Here's an example scenario: - - ```lua - local CoinsForPlayers = {651, 8801, 371, 79, 918, 0, 46, 183} - - print(Statistics.GetMean(CoinsForPlayers)) -- Get the average amount of coins each player has, keep in mind 8801 will skew the data. - -- Output: 1381.125 - ``` - - @param numberList {number} -- The dataset to perform the action on. - - @return number -]=] -function Statistics.GetMean(numberList: {number}): number - local Total = 0 - - for index, number in numberList do - Total += number - end - - return Total / #numberList -end - ---[=[ - Gets the number that occurs most in the provided dataset, nil if none or each number occurs the same amount of times. More info can be found [here](https://en.wikipedia.org/wiki/Mode_(statistics)) - - @param numberList {number} -- The dataset to perform the action on. - - @return number? -]=] -function Statistics.GetMode(numberList: {number}): number? - local CalculatedList = { } - - table.sort(numberList) - - for index, number in numberList do - if not CalculatedList[number] then - CalculatedList[number] = 1 - continue - end - - CalculatedList[number] += 1 - end - - local SortedList = Utility.DictionaryToArray(CalculatedList) - - table.sort(SortedList, function(a, b) - return a[2] > b[2] - end) - - if SortedList[1][1] == 1 and CalculatedList[SortedList[1][1]] == 1 then - return nil - end - - return SortedList[1][1] -end - -return Statistics \ No newline at end of file diff --git a/src/lua/libraries/benchmark/init.luau b/src/lua/libraries/benchmark/init.luau index f67337f8..856a5c3e 100644 --- a/src/lua/libraries/benchmark/init.luau +++ b/src/lua/libraries/benchmark/init.luau @@ -83,7 +83,7 @@ function Benchmark.CreateBenchmark() local self = setmetatable({ }, {__index = BenchmarkObject}) self.IsCompleted = false - self.Destroying = Signal.new() + self.Destroying = Signal() self.StartTime = 0 self.EndTime = 0 @@ -176,6 +176,8 @@ end ]=] function BenchmarkObject:Destroy() self.Destroying:Fire() + self.Destroying:DisconnectAll() + table.clear(self) setmetatable(self, nil) end diff --git a/src/lua/libraries/signal/Spawn.luau b/src/lua/libraries/signal/Spawn.luau new file mode 100644 index 00000000..b52dbe2b --- /dev/null +++ b/src/lua/libraries/signal/Spawn.luau @@ -0,0 +1,25 @@ +-- // by jackdotink + +local FreeThread: thread? = nil + +local function FunctionPasser(Callback, ...) + local AquiredThread = FreeThread + FreeThread = nil + Callback(...) + FreeThread = AquiredThread +end + +local function Yielder() + while true do + FunctionPasser(coroutine.yield()) + end +end + +return function(Callback: (T...) -> (), ...: T...) + if not FreeThread then + FreeThread = coroutine.create(Yielder) + coroutine.resume(FreeThread :: any) + end + + task.spawn(FreeThread :: thread, Callback, ...) +end \ No newline at end of file diff --git a/src/lua/libraries/signal/init.luau b/src/lua/libraries/signal/init.luau new file mode 100644 index 00000000..fb3e7a05 --- /dev/null +++ b/src/lua/libraries/signal/init.luau @@ -0,0 +1,72 @@ +local Spawn = require(script.Parent.Spawn) + +type SignalNode = { + Next: SignalNode?, + Callback: (T...) -> (), +} + +export type Signal = { + Root: SignalNode?, + + Connect: (self: Signal, Callback: (T...) -> ()) -> () -> (), + Wait: (self: Signal) -> T..., + Fire: (self: Signal, T...) -> (), + DisconnectAll: (self: Signal) -> (), +} + +local Signal = {} +Signal.__index = Signal + +function Signal.Connect(self: Signal, Callback: (T...) -> ()): () -> () + local Node = { + Next = self.Root, + Callback = Callback, + } + + self.Root = Node + + return function() + if self.Root == Node then + self.Root = Node.Next + else + local Current = self.Root + while Current do + if Current.Next == Node then + Current.Next = Node.Next + break + end + Current = Current.Next + end + end + end +end + +function Signal.Wait(self: Signal): T... + local Thread = coroutine.running() + local Disconnect + + Disconnect = self:Connect(function(...) + Disconnect() + coroutine.resume(Thread, ...) + end) + + return coroutine.yield() +end + +function Signal.Fire(self: Signal, ...: T...) + local Current = self.Root + while Current do + Spawn(Current.Callback, ...) + Current = Current.Next + end +end + +function Signal.DisconnectAll(self: Signal) + self.Root = nil +end + +return function(): Signal + return setmetatable({ + Root = nil, + }, Signal) :: any +end \ No newline at end of file diff --git a/src/ts/README.md b/src/ts/README.md new file mode 100644 index 00000000..080368c6 --- /dev/null +++ b/src/ts/README.md @@ -0,0 +1 @@ +TypeScript definition files are currently incomplete. \ No newline at end of file