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