From 2f7c1ece3eb9c10402117ad0819803d7380961f8 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 5 Oct 2023 21:38:06 -0500 Subject: [PATCH] v4 rc-2 commit --- docs/.vitepress/config.mts | 5 +- docs/api/controllers/network/client.md | 10 +- docs/api/controllers/network/server.md | 41 ++++- docs/start/features.md | 5 +- docs/start/installation.md | 10 +- docs/tutorial/networking.md | 4 +- docs/tutorial/update.md | 23 --- plugin/settings/settings.json | 2 +- plugin/src/json/structure.json | 53 +++++- .../NetworkController/Vendor/RedEvent.luau | 5 - .../Vendor/NetworkController/init.luau | 158 ++++++++++++++---- .../Vendor/Libraries/RedbloxUtils/Future.luau | 82 --------- .../Vendor/Libraries/RedbloxUtils/Signal.luau | 72 -------- .../Vendor/Libraries/RedbloxUtils/Spawn.luau | 25 --- src/lua/framework/Vendor/Types.luau | 3 +- src/lua/framework/init.luau | 14 ++ src/lua/libraries/signal/Spawn.luau | 25 --- src/lua/libraries/signal/init.luau | 72 -------- 18 files changed, 253 insertions(+), 356 deletions(-) delete mode 100644 docs/tutorial/update.md delete mode 100644 src/lua/framework/Vendor/Controllers/Vendor/NetworkController/Vendor/RedEvent.luau delete mode 100644 src/lua/framework/Vendor/Libraries/RedbloxUtils/Future.luau delete mode 100644 src/lua/framework/Vendor/Libraries/RedbloxUtils/Signal.luau delete mode 100644 src/lua/framework/Vendor/Libraries/RedbloxUtils/Spawn.luau delete mode 100644 src/lua/libraries/signal/Spawn.luau delete mode 100644 src/lua/libraries/signal/init.luau diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 9e616ed5..361a99f6 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -2,7 +2,7 @@ import { defineConfig } from 'vitepress' // https://vitepress.dev/reference/site-config export default defineConfig({ - head: [['link', { rel: 'icon', href: 'static/images/logo.png' }]], + head: [['link', { rel: 'icon', href: 'static/images/logo' }]], base: "/CanaryEngine/", title: "CanaryEngine", titleTemplate: "Canary Docs", @@ -15,7 +15,7 @@ export default defineConfig({ text: 'Guides', items: [ { text: 'Get Started', link: '/start/intro'}, - { text: 'Usage', link: '/tutorial/update' }, + { text: 'Usage', link: '/tutorial/packages' }, { text: 'Libraries', link: '/tutorial/libraries/benchmark' } ] }, @@ -67,7 +67,6 @@ export default defineConfig({ { text: 'Management', items: [ - { text: 'Update', link: '/tutorial/update' }, { text: 'Packages', link: '/tutorial/packages' }, { text: 'Structure', link: '/tutorial/structure' }, { text: 'Style Guide', link: '/tutorial/styleguide' }, diff --git a/docs/api/controllers/network/client.md b/docs/api/controllers/network/client.md index 82a1af9a..45b95e07 100644 --- a/docs/api/controllers/network/client.md +++ b/docs/api/controllers/network/client.md @@ -10,6 +10,14 @@ The name of the the network controller. * **string** +--- + +### IsListening + +Whether or not the network controller is subscribed to an event. + +* **boolean** + ## Methods ### Fire @@ -52,7 +60,7 @@ The data to invoke the server with ### Listen -Listens for the network controller to be fired by the server, then runs the provided function. +Listens for the network controller to be fired by the server, then runs the provided function. Note that this can only be run once, it will error if run more than once. **Parameters** diff --git a/docs/api/controllers/network/server.md b/docs/api/controllers/network/server.md index 3aff9de0..8b5ccc93 100644 --- a/docs/api/controllers/network/server.md +++ b/docs/api/controllers/network/server.md @@ -10,6 +10,22 @@ The name of the the network controller. * **string** +--- + +### IsListening + +Whether or not the network controller is subscribed to an event. + +* **boolean** + +--- + +### IsBinded + +Whether or not the network controller is binded to any invocations. + +* **boolean** + ## Methods ### Fire @@ -92,6 +108,24 @@ The data that should be sent to each player within `maximumRange` --- +### FireFilter + +Fires an event with a filter function, and runs the provided filter on every player in the server. + +**Parameters** + +* **filter:** `(Player) -> (boolean)`\ +The filter to run on each player, return a boolean to indicate that the player meets the threshold + +* **data:** `(Array | any)?`\ +The data that should be sent to each player that meets the threshold for `filter` + +**Returns** + +* **void** + +--- + ### Listen Listens for the network controller to be fired by the client, then runs the provided function. @@ -128,8 +162,11 @@ Sets a rate limit that is applied when invoking or firing a network controller f **Parameters** -* **maxInvokesPerSecond:** `number`\ -The maximum amount of invokes allowed per second, set to -1 to disable the rate limit +* **maxCalls:** `number`\ +The maximum amount of invokes allowed every `interval` seconds; set to -1 to disable the rate limit + +* **interval:** `number?`\ +The interval of which `maxCalls` is reset * **invokeOverflowCallback:** `((sender: Player) -> ())?`\ The callback function to run when the player has exceeded the current rate limit diff --git a/docs/start/features.md b/docs/start/features.md index d0bdab93..ed0cdf93 100644 --- a/docs/start/features.md +++ b/docs/start/features.md @@ -16,9 +16,12 @@ The API is aimed to be completely separate the server and the client, which allo The entire framework is documented, along with tutorials on each subject. * **Optimized** 🏃‍♀️ -Extremely optimized, with most internal functions running fast along with custom signal and network implementations. +Extremely optimized, with most internal functions running fast along with the custom signal and network implementations. * **Secure** 🔐 The networking system that CanaryEngine uses can actually partially prevent exploits like RemoteSpy from being easily useable. +* **Ordered** 🔄 +You can import modules from a main script, which will then execute in order. Can prevent issues like race conditions which come up a lot in multi-threaded architectures. + ... and much more! \ No newline at end of file diff --git a/docs/start/installation.md b/docs/start/installation.md index 0ee46e7a..eb17a7d0 100644 --- a/docs/start/installation.md +++ b/docs/start/installation.md @@ -18,8 +18,10 @@ keywords: [roblox, game, framework, install, tutorial, github, node] 3. Open Roblox Studio, and drag `CanaryStudioPlugin.rbxm` from your download directory into studio. 4. Right click the plugin, and choose `Save as Local Plugin` -### NPM (rbxts) +### Rojo -1. Open the command line of your choice, set the directory to the project folder that you wish. -2. Install Node if you haven't already, you can find that [here](https://nodejs.org/en/download) -3. After installing Node, enter the following in your command line: `npm i @rbxts/canaryengine` \ No newline at end of file +Rojo support is being looked into! + +### TypeScript + +We're currently working on definition files; we will not be publishing a package on roblox-ts. \ No newline at end of file diff --git a/docs/tutorial/networking.md b/docs/tutorial/networking.md index 02d30c06..1a538b73 100644 --- a/docs/tutorial/networking.md +++ b/docs/tutorial/networking.md @@ -23,7 +23,7 @@ Now lets continue this code and make it so it can recieve info from the server: ```lua local SendInfoNetwork = CanaryEngineClient.CreateNetworkController("SendInfoNetwork") -SendInfoNetwork:Connect(function(data) +SendInfoNetwork:Listen(function(data) print(data) end) ``` @@ -71,7 +71,7 @@ The [RemoteFunction](https://create.roblox.com/docs/reference/engine/classes/Rem ```lua local ValueGetNetwork = CanaryEngineServer.CreateNetworkController("ValueGetNetwork") -ValueGetNetwork:OnInvoke(function(sender, data) +ValueGetNetwork:BindToInvocation(function(sender, data) print(sender.Name) -- The player who sent the invoke's name if data[1] then return "yes" -- We must return a value here, or it will error diff --git a/docs/tutorial/update.md b/docs/tutorial/update.md deleted file mode 100644 index 60b45460..00000000 --- a/docs/tutorial/update.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Update -description: A tutorial on how to correctly update CanaryEngine when a new version releases -keywords: [roblox, game, framework, update, tutorial] ---- - -# Framework Updates - -To some, updating can be a lengthy and confusing process. With this tutorial, we hope to minimize the amount of people that are asking for help during the update process. - -### Reminders - -Whenever we create a new release on github, these changes are automatically pushed to the plugin via `HttpService`. This means that once you open studio, you will be prompted to update your version of CanaryEngine. Note that sometimes you will also have to update your plugin as well, so check to make sure that there are no actual plugin updates available. Not updating your plugin could result in some unexpected behavior such as this: - -![Out of date plugin error example](images/update-images/error-example.png) - -### Update Process - -First and foremost, update your Canary Studio plugin. This ensures that upon updating, your plugin is the most up-to-date and errors aren't likely to happen. You can find the plugin manager here: - -![Roblox plugin manager](images/update-images/where-to-update.png) - -If a plugin update is available, make sure to update your plugin for reasons mentioned before. Now that you have updated your plugin, open the Canary Studio `Install Settings` menu. This allows you to install, update, and uninstall frameworks. For updating, make sure to press the update button. You will be prompted to update, and then you are done! \ No newline at end of file diff --git a/plugin/settings/settings.json b/plugin/settings/settings.json index 02d34a30..7bcaf176 100644 --- a/plugin/settings/settings.json +++ b/plugin/settings/settings.json @@ -1,5 +1,5 @@ { - "Files": 14, + "Files": 21, "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/structure.json b/plugin/src/json/structure.json index f02a7ded..82882ac9 100644 --- a/plugin/src/json/structure.json +++ b/plugin/src/json/structure.json @@ -56,7 +56,50 @@ "NetworkController": { "Events": { }, "Vendor": { - "BridgeNet2": "BridgeNet2Package" + "Ratelimit": { + "$className": "ModuleScript", + "$properties": { + "Source": "https://raw.githubusercontent.com/red-blox/Util/62037a647038af2b92129435957bace986452ff1/libs/Ratelimit/Ratelimit.luau" + } + }, + "Guard": { + "$className": "ModuleScript", + "$properties": { + "Source": "https://raw.githubusercontent.com/red-blox/Util/1f9216713d15579bdf1744235448fbac702aa54c/libs/Guard/Guard.luau" + } + }, + "Future": { + "$className": "ModuleScript", + "$properties": { + "Source": "https://raw.githubusercontent.com/red-blox/Util/76281011d28f083d2d659ef4c1b567270192aeb3/libs/Future/Future.luau" + } + }, + "Spawn": { + "$className": "ModuleScript", + "$properties": { + "Source": "https://raw.githubusercontent.com/red-blox/Util/fd238ef1d4771a7aaa83b2f0650527f01337ce24/libs/Spawn/Spawn.luau" + } + }, + "Red": { + "ClientEvent": { + "$className": "ModuleScript", + "$properties": { + "Source": "https://raw.githubusercontent.com/red-blox/Red/ffd21a7a776873751c24e7e1ed4cf517564cd5f7/lib/ClientEvent.luau" + } + }, + "ServerEvent": { + "$className": "ModuleScript", + "$properties": { + "Source": "https://raw.githubusercontent.com/red-blox/Red/ffd21a7a776873751c24e7e1ed4cf517564cd5f7/lib/ServerEvent.luau" + } + }, + "Identifier": { + "$className": "ModuleScript", + "$properties": { + "Source": "https://raw.githubusercontent.com/red-blox/Red/ffd21a7a776873751c24e7e1ed4cf517564cd5f7/lib/Identifier.luau" + } + } + } }, "Init": { "$className": "ModuleScript", @@ -97,24 +140,24 @@ } } } - }, + }, "RedbloxUtils": { "Future": { "$className": "ModuleScript", "$properties": { - "Source": "https://raw.githubusercontent.com/canary-development/CanaryEngine/main/src/lua/framework/Vendor/Libraries/RedbloxUtils/Future.luau" + "Source": "https://raw.githubusercontent.com/red-blox/Util/76281011d28f083d2d659ef4c1b567270192aeb3/libs/Future/Future.luau" } }, "Signal": { "$className": "ModuleScript", "$properties": { - "Source": "https://raw.githubusercontent.com/canary-development/CanaryEngine/main/src/lua/framework/Vendor/Libraries/RedbloxUtils/Signal.luau" + "Source": "https://raw.githubusercontent.com/red-blox/Util/406727b68d204058ed385f0055a5508dfdd71733/libs/Signal/Signal.luau" } }, "Spawn": { "$className": "ModuleScript", "$properties": { - "Source": "https://raw.githubusercontent.com/canary-development/CanaryEngine/main/src/lua/framework/Vendor/Libraries/RedbloxUtils/Spawn.luau" + "Source": "https://raw.githubusercontent.com/red-blox/Util/fd238ef1d4771a7aaa83b2f0650527f01337ce24/libs/Spawn/Spawn.luau" } } } diff --git a/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/Vendor/RedEvent.luau b/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/Vendor/RedEvent.luau deleted file mode 100644 index 55e6b383..00000000 --- a/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/Vendor/RedEvent.luau +++ /dev/null @@ -1,5 +0,0 @@ -local Red = require(script.Parent.Parent.Vendor.Red) - -return Red.Event(function() - return -end) \ No newline at end of file diff --git a/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/init.luau b/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/init.luau index d088f3bb..fbad5220 100644 --- a/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/init.luau +++ b/src/lua/framework/Vendor/Controllers/Vendor/NetworkController/init.luau @@ -10,6 +10,7 @@ local Vendor = script.Parent.Vendor local EngineVendor = Vendor.Parent.Parent.Parent.Parent +local ReplicatedStorage = game:GetService("ReplicatedStorage") local PlayerService = game:GetService("Players") local RunService = game:GetService("RunService") @@ -19,18 +20,39 @@ local Indexes = { } local Future = require(EngineVendor.Libraries.RedbloxUtils.Future) -local BridgeNet = require(Vendor.BridgeNet2.BridgeNet2) +local Ratelimit = require(Vendor.Ratelimit) local Sanitize = require(Vendor.Parent.Parent.Sanitize) +local RedServerEvent = require(Vendor.Red.ServerEvent) +local RedClientEvent = require(Vendor.Red.ClientEvent) + +local MainRedRemote: RemoteEvent +local ClientFolder: ScreenGui + -- // Functions +local function ValidateTuple(...: any) + if select("#", ...) > 1 then + warn("Invalid data") + return false + end + return true +end + -- // Constructors function NetworkController.CreateController(name: string) - local self = setmetatable({ }, RunService:IsClient() and Indexes[1] or Indexes[2]) + local self = setmetatable({ }, if RunService:IsClient() then Indexes[1] else Indexes[2]) self.Name = name - self._Event = BridgeNet.ReferenceBridge(name) + self.IsListening = false + self.IsBinded = nil + + self._Ratelimit = nil + self._InvokeOverflow = nil + + self._EventName = `{name}_E` + self._FunctionName = `{name}_F` return self end @@ -38,78 +60,150 @@ end -- Network Controller Client function NetworkControllerClient:Fire(data: ({any} | any)?) - self._Event:Fire(Sanitize(data)) + RedClientEvent.Fire(self._EventName, Sanitize(data)) end function NetworkControllerClient:Listen(func: (data: {any}?) -> ()) - self._Event:Connect(func) + assert(not self.IsListening, "Controller is already subscribed to an event") + self.IsListening = true + + RedClientEvent.Listen(self._EventName, func) end -function NetworkControllerClient:InvokeAsync(data: ({any} | any)?) - return Future.new(function() - return Sanitize(self._Event:InvokeServerAsync(Sanitize(data))) - end) +function NetworkControllerClient:InvokeAsync(data: ({any} | any)?): Future.Future + return RedClientEvent.Call(self._FunctionName, Sanitize(data)) end -- Network Controller Server function NetworkControllerServer:Fire(recipients: Player | {Player}, data: ({any} | any)?) - if type(recipients) ~= "table" then - self._Event:Fire(recipients, Sanitize(data)) + if type(recipients) == "table" then + for _, player in recipients do + RedServerEvent.Fire(player, self._EventName, Sanitize(data)) + end + return end - self._Event:Fire(BridgeNet.Players(recipients), Sanitize(data)) + RedServerEvent.Fire(recipients, self._EventName, Sanitize(data)) end function NetworkControllerServer:FireAll(data: ({any} | any)?) - self._Event:Fire(BridgeNet.AllPlayers(), Sanitize(data)) + for _, player in PlayerService:GetPlayers() do + RedServerEvent.Fire(player, self._EventName, Sanitize(data)) + end end function NetworkControllerServer:FireExcept(except: Player | {Player}, data: ({any} | any)?) - if type(except) ~= "table" then - self._Event:Fire(BridgeNet.PlayersExcept({except}), Sanitize(data)) - return + if type(except) ~= "table" then -- don't use typeof(), for optimization + except = {except} end - self._Event:Fire(BridgeNet.PlayersExcept(except), Sanitize(data)) -end + for _, player in PlayerService:GetPlayers() do + if table.find(except, player) then + continue + end -function NetworkControllerServer:FireInRange(comparePoint: Vector3, maximumRange: number, data: ({any} | any)?) - local PlayersToFireTo = { } + RedServerEvent.Fire(player, self._EventName, Sanitize(data)) + end +end - for _, player: Player in PlayerService:GetPlayers() do - if player:DistanceFromCharacter(comparePoint) <= maximumRange then - table.insert(PlayersToFireTo, player) +function NetworkControllerServer:FireFilter(data: ({any} | any)?, filter: (Player) -> (boolean)) + for _, player in PlayerService:GetPlayers() do + if filter(player) then + RedServerEvent.Fire(player, self._EventName, Sanitize(data)) end end +end - self:Fire(PlayersToFireTo, data) +function NetworkControllerServer:FireInRange(comparePoint: Vector3, maximumRange: number, data: ({any} | any)?) + self:FireFilter(data, function(player) -- reuse FireFilter to follow D.R.Y. standards + if player:DistanceFromCharacter(comparePoint) <= maximumRange then + return true + end + end) end function NetworkControllerServer:Listen(func: (sender: Player, data: {any}?) -> ()) - self._Event:Connect(func) + assert(not self.IsListening, "Controller is already subscribed to an event") + self.IsListening = true + + RedServerEvent.Listen(self._EventName, function(player, ...) + if (self._Ratelimit and self._InvokeOverflow) and not self._Ratelimit(player) then + self._InvokeOverflow(player) + return + end + + local ValidData = ValidateTuple(...) + + if not ValidData then + return + end + + func(player, ...) + end) end -function NetworkControllerServer:SetRateLimit(maxInvokesPerSecond: number, invokeOverflowCallback: ((sender: Player) -> ())?) - if maxInvokesPerSecond <= -1 then - self._Bridge:DisableRateLimit() - return +function NetworkControllerServer:SetRateLimit(maxCalls: number, interval: number?, invokeOverflowCallback: ((sender: Player) -> ())?) + if maxCalls <= -1 then + self._Ratelimit = nil + self._InvokeOverflow = nil end - if not invokeOverflowCallback then + if not (interval or invokeOverflowCallback) then return end - self._Event:RateLimit(maxInvokesPerSecond, invokeOverflowCallback) + self._Ratelimit = Ratelimit(maxCalls, interval) + self._InvokeOverflow = invokeOverflowCallback end function NetworkControllerServer:BindToInvocation(callback: (sender: Player, data: {any}?) -> (({any} | any))) - self._Event.OnServerInvoke = callback + assert(not self.IsBinded, "Controller already has function binded to invocation") + self.IsBinded = true + + RedServerEvent.Listen(self._FunctionName, function(player, data) + return Sanitize(callback(player, Sanitize(data))) + end) end -- // Actions +if RunService:IsServer() then + if ReplicatedStorage:FindFirstChild("RedEvent") then + MainRedRemote = ReplicatedStorage:FindFirstChild("RedEvent") + else + MainRedRemote = Instance.new("RemoteEvent") + MainRedRemote.Name = "RedEvent" + MainRedRemote.Parent = ReplicatedStorage + end + + RedServerEvent.Start() + + local function PlayerAdded(player: Player) + local ClientFolderCreate = Instance.new("ScreenGui") + + ClientFolderCreate.Enabled = false + ClientFolderCreate.ResetOnSpawn = false + ClientFolderCreate.Name = "Red" + ClientFolderCreate.Parent = player:WaitForChild("PlayerGui") + end + + PlayerService.PlayerAdded:Connect(PlayerAdded) + + for _, player in PlayerService:GetPlayers() do + PlayerAdded(player) + end +else + local Player = PlayerService.LocalPlayer + MainRedRemote = ReplicatedStorage:FindFirstChild("RedEvent") + + RedClientEvent.Start() + + ClientFolder = Player:WaitForChild("PlayerGui"):WaitForChild("Red") + ClientFolder.Parent = nil +end + table.freeze(NetworkControllerServer) table.freeze(NetworkControllerClient) diff --git a/src/lua/framework/Vendor/Libraries/RedbloxUtils/Future.luau b/src/lua/framework/Vendor/Libraries/RedbloxUtils/Future.luau deleted file mode 100644 index ef02eb87..00000000 --- a/src/lua/framework/Vendor/Libraries/RedbloxUtils/Future.luau +++ /dev/null @@ -1,82 +0,0 @@ -local Spawn = require(script.Parent.Spawn) - -local Future = {} -Future.__index = Future - -function Future.new(Callback: (A...) -> T..., ...: A...) - local self = setmetatable({}, Future) - - self.ValueList = nil :: { any }? - - self.AfterList = {} :: { (T...) -> () } - self.YieldList = {} :: { thread } - - task.spawn(function(self, ...) - self.ValueList = { Callback(...) } - - for _, Thread in self.YieldList do - task.spawn(Thread, table.unpack(self.ValueList)) - end - - for _, Callback in self.AfterList do - Spawn(Callback, table.unpack(self.ValueList)) - end - end, self, ...) - - return self -end - -export type Future = typeof(Future.new(function(): T... end)) - -function Future.Try(Callback: (A...) -> T..., ...: A...) - return Future.new(pcall, Callback, ...) -end - -function Future.IsComplete(self: Future) - return self.ValueList ~= nil -end - -function Future.IsPending(self: Future) - return self.ValueList == nil -end - -function Future.Expect(self: Future, Message: string): T... - assert(self.ValueList, Message) - return table.unpack(self.ValueList) -end - -function Future.Unwrap(self: Future): T... - return self:Expect("Attempt to unwrap pending value!") -end - -function Future.UnwrapOr(self: Future, ...: T...) - if self.ValueList then - return table.unpack(self.ValueList) - else - return ... - end -end - -function Future.UnwrapOrElse(self: Future, Else: () -> T...) - if self.ValueList then - return table.unpack(self.ValueList) - else - return Else() - end -end - -function Future.After(self: Future, Callback: (T...) -> ()) - table.insert(self.AfterList, Callback) -end - -function Future.Await(self: Future): T... - if self.ValueList then - return table.unpack(self.ValueList) - else - table.insert(self.YieldList, coroutine.running()) - - return coroutine.yield() - end -end - -return Future \ 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 deleted file mode 100644 index fb3e7a05..00000000 --- a/src/lua/framework/Vendor/Libraries/RedbloxUtils/Signal.luau +++ /dev/null @@ -1,72 +0,0 @@ -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/lua/framework/Vendor/Libraries/RedbloxUtils/Spawn.luau b/src/lua/framework/Vendor/Libraries/RedbloxUtils/Spawn.luau deleted file mode 100644 index b52dbe2b..00000000 --- a/src/lua/framework/Vendor/Libraries/RedbloxUtils/Spawn.luau +++ /dev/null @@ -1,25 +0,0 @@ --- // 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/framework/Vendor/Types.luau b/src/lua/framework/Vendor/Types.luau index 438a86f9..d6e90436 100644 --- a/src/lua/framework/Vendor/Types.luau +++ b/src/lua/framework/Vendor/Types.luau @@ -38,12 +38,13 @@ export type ClientNetworkController = { export type ServerNetworkController = { Listen: (self: ServerNetworkController, func: (sender: Player, data: (Array | unknown)?) -> ()) -> (), - OnInvoke: (self: ServerNetworkController, callback: (sender: Player, data: (Array | unknown)?) -> (Array | U)) -> (), + BindToInvocation: (self: ServerNetworkController, callback: (sender: Player, data: (Array | unknown)?) -> (Array | U)) -> (), Fire: (self: ServerNetworkController, recipient: Player | Array, data: (Array | T)?) -> (), FireAll: (self: ServerNetworkController, data: (Array | T)?) -> (), FireExcept: (self: ServerNetworkController, except: Player | Array, data: (Array | T)?) -> (), FireInRange: (self: ServerNetworkController, comparePoint: Vector3, maximumRange: number, data: (Array | T)?) -> (), + FireFilter: (self: ServerNetworkController, data: (Array | T)?, filter: (Player) -> (boolean)) -> (), SetRateLimit: (self: ServerNetworkController, maxInvokesPerSecond: number, invokeOverflowCallback: (sender: Player) -> ()) -> (), Name: string, diff --git a/src/lua/framework/init.luau b/src/lua/framework/init.luau index 6fe43deb..5e4facb6 100644 --- a/src/lua/framework/init.luau +++ b/src/lua/framework/init.luau @@ -86,6 +86,20 @@ function CanaryEngine.GetEngineClient() end end +function CanaryEngine.ImportPackagesInOrder(importList: {ModuleScript}, importDeep: boolean?) + for _, package in importList do + require(package) + + if importDeep then + for _, deepPackage in package:GetDescendants() do + if deepPackage:IsA("ModuleScript") then + require(deepPackage) + end + end + end + end +end + -- Exclusive API for replicated will come soon function CanaryEngine.GetEngineReplicated() if table.isfrozen(CanaryEngineReplicated) then diff --git a/src/lua/libraries/signal/Spawn.luau b/src/lua/libraries/signal/Spawn.luau deleted file mode 100644 index b52dbe2b..00000000 --- a/src/lua/libraries/signal/Spawn.luau +++ /dev/null @@ -1,25 +0,0 @@ --- // 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 deleted file mode 100644 index fb3e7a05..00000000 --- a/src/lua/libraries/signal/init.luau +++ /dev/null @@ -1,72 +0,0 @@ -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