From 1247fc58a891d0504fbd62237a45750e72357d9a Mon Sep 17 00:00:00 2001
From: Javi <4343867+dig1t@users.noreply.github.com>
Date: Thu, 16 Jan 2025 16:30:23 +0000
Subject: [PATCH] cleanup
---
README.md | 9 +-
package.json | 3 +-
src/Controller.luau | 240 --------------------------------------
src/Server.luau | 277 --------------------------------------------
src/Store.luau | 162 ++++++++++++++------------
src/init.luau | 11 --
6 files changed, 89 insertions(+), 613 deletions(-)
delete mode 100644 src/Controller.luau
delete mode 100644 src/Server.luau
diff --git a/README.md b/README.md
index d90c017..daf596a 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
## What is red?
red is a lightweight, easy-to-use, and efficient event-driven library for Roblox.
-red provides a simple API that allows you to create and manage events, actions, and state in your game, making it easy to build complex systems and features.
+red provides a simple API that allows you to create and manage events, actions, and state in your game.
## Benefits
- **Simple API**: red is designed to be easy to use and understand, making it perfect for developers of all skill levels.
@@ -26,10 +26,3 @@ red = "dig1t/red@2.0.0"
Download the rbxl file from the [releases](https://github.com/dig1t/red/releases) tab.
Once the place file is open, you can find the package inside `ReplicatedStorage.Packages`.
-
-## Games powered by red
-
-
-
-
-Add your game here! Create a Pull Request to add your game to the list.
diff --git a/package.json b/package.json
index 1170725..42fb66d 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,6 @@
"build:rojo": "rojo build -o dev-build.rbxl build.project.json",
"build:docs": "moonwave build",
"build": "npm run build:dependencies && npm run build:rojo",
-
"dev": "npm run build && dev-build.rbxl && npm start",
"start": "rojo serve build.project.json"
},
@@ -23,6 +22,6 @@
},
"homepage": "https://github.com/dig1t/red#readme",
"dependencies": {
- "moonwave": "^1.1.3"
+ "moonwave": "^1.2.1"
}
}
diff --git a/src/Controller.luau b/src/Controller.luau
deleted file mode 100644
index 6261706..0000000
--- a/src/Controller.luau
+++ /dev/null
@@ -1,240 +0,0 @@
---[=[
-@class Controller
-
-Controller is a class that is used to manage different features of the game.
-It can be ran on the client and server.
-
-```lua
-local controller = red.Controller.new({
- name = "test";
-})
-
-controller:init(function()
- print("init")
-end)
-
--- You can subscribe to red actions through the controller
-controller:subscribe(function(action: red.Action)
- print(action.type)
-end)
-
--- You can also listen to the stepped event
-controller:stepped(function(deltaTime: number)
- self.timeElapsed = self.time + deltaTime
- print(self.timeElapsed)
-end)
-```
-]=]
-
---!strict
-
-local RunService = game:GetService("RunService")
-
-local Store = require(script.Parent.Store)
-local Types = require(script.Parent.Types)
-local Util = require(script.Parent.Parent.Util)
-
-local Controller = {}
-Controller.__index = Controller
-
--- Controller
-export type ControllerConfig = {
- name: string,
- [any]: any,
-}
-
-export type ControllerType = typeof(setmetatable(
- {} :: {
- name: string,
- controllerId: string,
- redController: boolean,
- [any]: any,
- },
- Controller
-))
-
-local store = Store.new()
-
-local controllers: { [string]: ControllerType } = {}
-local storeSubscriptions: { [number]: string } = {}
-
-local _subscriptionId: string = store:subscribe(function(action: Types.Action)
- for _, controllerId: string in pairs(storeSubscriptions) do
- local controller: ControllerType = controllers[controllerId]
-
- if controller and controller.subscribe then
- controller:subscribe(action)
- end
- end
-end)
-
-RunService.Stepped:Connect(function(deltaTime: number)
- for _, controller: ControllerType in pairs(controllers) do
- if controller.stepped then
- controller:stepped(deltaTime)
- end
- end
-end)
-
---[=[
-Creates a new controller instance
-
-@param config ControllerConfig
-@return ControllerType
-]=]
-function Controller.new(config: ControllerConfig): ControllerType
- assert(typeof(config) == "table", "Expected config to be a table")
- assert(typeof(config.name) == "string", "Expected config.name to be a string")
- assert(
- not controllers[config.name],
- "Controller with name " .. config.name .. " already exists"
- )
-
- if config.init then
- assert(typeof(config.init) == "function", "Expected config.init to be a function")
- end
-
- if config.subscribe then
- assert(
- typeof(config.subscribe) == "function",
- "Expected config.subscribe to be a function"
- )
- end
-
- local self = setmetatable({}, Controller)
-
- self.name = config.name
- self.controllerId = Util.randomString()
- self.redController = true
-
- return self
-end
-
---[=[
-Called when the controller is initialized
-
-@within Controller
-@method Destroy
-]=]
-function Controller.Destroy(self: ControllerType)
- controllers[self.name] = nil
-
- local subscriptionIndex = table.find(storeSubscriptions, self.controllerId)
-
- if subscriptionIndex then
- table.remove(storeSubscriptions, subscriptionIndex)
- end
-end
-
---[=[
-Adds controller ModuleScripts from a folder or table
-
-Setting a requiredSuffix will only add modules that end with the suffix.
-(e.g. "Controller" will only add modules that end with "Controller")
-
-This will call the ```init``` method on all controllers once they are added.
-
-Call ```Controller.start()``` to call the ```ready``` method on all controllers.
-
-@param _controllers Instance | { ModuleScript }
-@param requiredSuffix string?
-]=]
-function Controller.addModules(
- _controllers: Instance | { ModuleScript },
- requiredSuffix: string?
-)
- local modules: { ModuleScript } = {}
-
- if typeof(_controllers) == "Instance" then
- if requiredSuffix then
- local children: { Instance } = _controllers:GetDescendants()
-
- _controllers = {}
-
- for _, module: Instance in ipairs(children) do
- if
- module
- and module:IsA("ModuleScript")
- and module.Name:sub(-#requiredSuffix) == requiredSuffix
- then
- local _module: ModuleScript = module
- modules[#modules + 1] = _module
- end
- end
- else
- for _, child: Instance in ipairs(_controllers:GetChildren()) do
- if child and child:IsA("ModuleScript") then
- modules[#modules + 1] = child
- end
- end
- end
- elseif typeof(_controllers) == "table" then
- for _, module: ModuleScript in ipairs(_controllers) do
- assert(typeof(module) == "Instance", "Expected module to be an Instance")
- end
- end
-
- for _, module: ModuleScript in pairs(modules) do
- local newController: ControllerType = require(module) :: any
-
- if not newController.name then
- warn(`{module.Name} does not have a name`)
- continue
- end
-
- if newController.init then
- assert(
- typeof(newController.init) == "function",
- "Expected controller.init to be a function"
- )
-
- task.spawn(function()
- debug.setmemorycategory(newController.name)
- newController:init()
- end)
- end
-
- if newController.subscribe then
- storeSubscriptions[#storeSubscriptions + 1] = newController.controllerId
- end
-
- controllers[newController.name] = newController
- end
-end
-
---- Calls the ```ready``` method on all controllers.
-function Controller.start()
- for _, controller: ControllerType in pairs(controllers) do
- if not controller.ready then
- continue
- end
-
- assert(
- typeof(controller.ready) == "function",
- "Expected controller.ready to be a function"
- )
-
- task.spawn(function()
- debug.setmemorycategory(controller.name)
- controller:ready()
- end)
- end
-end
-
---[=[
-Gets a controller by name
-
-@param name string
-@return ControllerType?
-]=]
-function Controller.get(name: string): ControllerType?
- assert(typeof(name) == "string", "Expected name to be a string")
-
- if not controllers[name] then
- warn(`Controller with name {name} does not exist`)
- end
-
- return controllers[name]
-end
-
-return Controller
diff --git a/src/Server.luau b/src/Server.luau
deleted file mode 100644
index e11e7bc..0000000
--- a/src/Server.luau
+++ /dev/null
@@ -1,277 +0,0 @@
---[=[
-@class Server
-
-The Server class is used to create a new server instance.
-
-#### Setup
-```lua
-local ReplicatedStorage = game:GetService("ReplicatedStorage")
-
-local red = require(ReplicatedStorage.Packages.red)
-
-local server: red.ServerType = red.Server.new() -- Constructs a new server
-
-server:init() -- Starts listening to dispatches
-```
-]=]
-
---!strict
-
-local Store = require(script.Parent.Store)
-local Types = require(script.Parent.Types)
-local redUtil = require(script.Parent.redUtil)
-
-local remotes = redUtil.getRemotes()
-local store = Store.new()
-
-local Server = {}
-Server.__index = Server
-
-export type ServerType = typeof(setmetatable(
- {} :: {
- _reducers: {
- [string]: (player: Player, payload: Types.ActionPayload?) -> any,
- },
- started: boolean,
- },
- Server
-))
-
---[=[
-Creates a new server instance
-
-@return ServerType
-]=]
-function Server.new(): ServerType
- return setmetatable({
- _reducers = {},
- started = false,
- store = store,
- }, Server) :: ServerType
-end
-
---[=[
-Use this method to bind a reducer to the server.
-
-Examples:
-```lua
-server:bind("ACTION_TYPE", function(player: Player, payload: Types.ActionPayload?)
- -- Do something
-end)
-```
-
-```lua
-server:bind("PLAYER_KILL", function(player)
- if player and player.Character then
- player.Character:BreakJoints()
- end
-end, true)
-```
-
-@within Server
-@method bind
-@param actionType string
-@param reducer () -> ()
-]=]
-function Server.bind(
- self: ServerType,
- actionType: string,
- reducer: (player: Player, payload: Types.ActionPayload) -> ()
-)
- assert(typeof(actionType) == "string", "Action type must be a string")
- assert(typeof(reducer) == "function", "Action must be a function")
-
- self._reducers[actionType] = reducer
-end
-
---[=[
-Use this method to unbind a handler from the server.
-
-Example:
-```lua
-server:unbind("ACTION_TYPE")
-```
-
-@within Server
-@method unbind
-@param id string
-]=]
-function Server.unbind(self: ServerType, id: string)
- assert(self._reducers[id], "Action reducer does not exist")
-
- self._reducers[id] = nil
-end
-
---[=[
-Use this method to load a handler into the server.
-
-Example:
-```lua
-server:useHandler(game.ServerScriptService.Server.ActionHandlers.Players)
-```
-
-@within Server
-@method useHandler
-@param moduleInstance ModuleScript
-]=]
-function Server.useHandler(self: ServerType, moduleInstance: ModuleScript)
- assert(
- typeof(moduleInstance) == "Instance" and moduleInstance:IsA("ModuleScript"),
- "Path is not a ModuleScript"
- )
-
- local handler = require(moduleInstance) :: any
-
- assert(typeof(handler) == "function", "Handler must be a function")
-
- debug.setmemorycategory(`Action Handler: {moduleInstance.Name}`)
- handler(self)
-end
-
---[=[
-Use this method to load multiple handlers modules into the server.
-
-Example:
-```lua
-server:useHandlers(game.ServerScriptService.Server.Handlers:GetChildren())
-```
-
-@within Server
-@method useHandlers
-@param handlers { ModuleScript }
-]=]
-function Server.useHandlers(self: ServerType, handlers: { ModuleScript } | Instance)
- for _, handler: ModuleScript in
- pairs(
- typeof(handlers) == "Instance" and handlers:GetChildren()
- or handlers :: { ModuleScript }
- )
- do
- self:useHandler(handler)
- end
-end
-
---[=[
-Use this method to call an action.
-
-Example:
-```lua
-server:_call(action, player)
-```
-
-@within Server
-@method _call
-@param action Types.Action
-@param player Player?
-@return Types.Action
-]=]
-function Server._call(
- self: ServerType,
- action: Types.Action,
- player: Player
-): Types.Action
- assert(action and typeof(action.type) == "string", "Action type must be a string")
-
- if not self._reducers[action.type] then
- return {
- type = action.type,
- err = "Action does not exist",
- success = false,
- } :: Types.Action
- end
-
- local success: boolean, res: Types.Action | string =
- pcall(self._reducers[action.type], player, action.payload)
-
- if not success then
- task.spawn(function()
- local userId: number = player.UserId
-
- if typeof(res) == "string" then
- error(res .. (userId and ` - UserId: {userId}` or ""), 2)
- end
- end)
-
- return {
- type = action.type,
- err = typeof(res) == "string" and res or nil,
- success = false,
- } :: Types.Action
- end
-
- -- Add a success property to the action
- if typeof(res) == "table" and not res.err and res.success == nil then
- res.success = true
- elseif not res then
- res = {
- type = action.type,
- success = true,
- }
- end
-
- return res :: Types.Action
-end
-
---[=[
-Initializes the server.
-
-@within Server
-@method listen
-]=]
-function Server.listen(self: ServerType)
- -- store:dispatch() called from client
- remotes.Client.OnServerEvent:Connect(
- function(player: Player, action: Types.Action)
- if action.method and action.method == "get_result" then
- return
- end
-
- local res: Types.Action = self:_call(action, player)
-
- -- If the action's method is "get" fire it back
- -- to the sender
- if action.method and action.method == "get" then
- assert(
- typeof(res) == "table",
- action.type
- .. " - Cannot return a "
- .. typeof(res)
- .. " type to a client."
- )
-
- -- Change method to get result since
- -- the server is now firing the result
- -- to the client.
- res.method = "get_result"
- res.uid = action.uid
-
- remotes.Client:FireClient(player, res)
- end
- end
- )
-
- self.started = true
-end
-
---[=[
-Use this method to dispatch an action to the store from a handler
-
-Example:
-```lua
-server:dispatch({
- type = "ACTION_TYPE",
- payload = {
- -- Payload
- }
-})
-```
-
-@within Server
-@method dispatch
-@param ... any
-]=]
-function Server.dispatch(_, ...)
- store:dispatch(...)
-end
-
-return Server
diff --git a/src/Store.luau b/src/Store.luau
index 4c4da56..5eaeaed 100644
--- a/src/Store.luau
+++ b/src/Store.luau
@@ -1,6 +1,6 @@
--[=[
@class Store
-
+
Dispatcher for actions to be dispatched to the server or clients.
#### Setup
@@ -9,32 +9,30 @@
local red = require(ReplicatedStorage.Packages.red)
- local store: red.StoreType = red.Store.new() -- Constructs a new store
-
-- Client
- store:subscribe(function(action: red.Action)
+ red.Store.subscribe(function(action: red.Action)
print(action.type)
end)
-
- store:dispatch({
+
+ red.Store.dispatch({
type = "HELLO_WORLD",
payload = {
message = "Hello World"
}
})
-
+
-- Server
-
+
-- Dispatch to all clients
- store:dispatch(true, {
+ red.Store.dispatch(true, {
type = "HELLO_WORLD",
payload = {
message = "Hello World"
}
})
-
+
-- Dispatch to one client
- store:dispatch(Players.Player1, {
+ red.Store.dispatch(Players.Player1, {
type = "HELLO_WORLD",
payload = {
message = "Hello World"
@@ -61,13 +59,8 @@ Store.__index = Store
local _middleware: { (action: Types.Action) -> () } = {}
-export type StoreType = typeof(setmetatable(
- {} :: {
- _connections: { [number]: RBXScriptConnection },
- _subscribers: { [Types.SubscriptionId]: (action: Types.Action) -> nil },
- },
- Store
-))
+local _connections: { [number]: RBXScriptConnection } = {}
+local _subscribers: { [Types.SubscriptionId]: (action: Types.Action) -> nil } = {}
--[=[
Applies middleware to the store
@@ -75,13 +68,13 @@ export type StoreType = typeof(setmetatable(
The middleware function will be called before the action is dispatched.
```lua
- store:use(function(action: red.Action)
+ store.use(function(action: red.Action)
if action.type == "HELLO_WORLD" then
action.payload.message = action.payload.message .. "!"
end
end)
- store:dispatch({
+ store.dispatch({
type = "HELLO_WORLD",
payload = {
message = "Hello World"
@@ -99,20 +92,6 @@ function Store.use(middleware: (action: Types.Action) -> ())
_middleware[#_middleware + 1] = middleware
end
---[=[
- Creates a new store instance
-
- @return StoreType
-]=]
-function Store.new(): StoreType
- local self = setmetatable({}, Store) :: StoreType
-
- self._connections = {}
- self._subscribers = {}
-
- return self
-end
-
--[=[
Dispatches an action
@@ -126,12 +105,12 @@ end
local Players = game:GetService("Players")
-- Dispatch to the server
- store:dispatch({
+ store.dispatch({
type = "PLAYER_KILL",
player = Players.Player1 -- The first argument in the binded action.
})
- store:dispatch({
+ store.dispatch({
type = "PLAYER_DAMAGE",
player = { Players.Player2, Players.Player3 }
payload = { -- The second argument in the binded action.
@@ -139,7 +118,7 @@ end
}
})
- store:dispatch({ -- Called from the server
+ store.dispatch({ -- Called from the server
type = "GAME_STOP",
payload = { -- This would be the first argument since there is no reason to include a player parameter.
message = "Game over"
@@ -147,7 +126,7 @@ end
})
-- Dispatch to all clients
- store:dispatch(true, {
+ store.dispatch(true, {
type = "UI_NOTIFICATION",
payload = {
text = "Hello World!"
@@ -155,12 +134,12 @@ end
})
-- Dispatch to one client
- store:dispatch(Players.Player1, {
+ store.dispatch(Players.Player1, {
type = "UI_SPECTATE_START"
})
-- Dispatch to multiple clients
- store:dispatch({ Players.Player2, Players.Player3 }, {
+ store.dispatch({ Players.Player2, Players.Player3 }, {
type = "UI_GAME_TIMER",
payload = {
duration = 60 -- Show a countdown timer lasting 60 seconds
@@ -173,7 +152,7 @@ end
@param target Player | { Player } | true
@param action Types.Action
]=]
-function Store.dispatch(_self: StoreType, ...: any)
+function Store.dispatch(...: any)
local args: { any } = { ... }
local action: Types.Action = #args == 1 and args[1] or args[2]
@@ -242,7 +221,7 @@ end
```lua
-- Client
- local fetch = store:get({ -- Fetch player stats from the server
+ local fetch = store.get({ -- Fetch player stats from the server
type = "PLAYER_STATS",
})
local stats = fetch.success and fetch.payload.stats
@@ -254,14 +233,13 @@ end
@within Store
@method get
- @param self StoreType
@param action Types.Action
@return Types.Action
]=]
-function Store.get(self: StoreType, action: Types.Action): Types.Action
+function Store.get(action: Types.Action): Types.Action
assert(typeof(action) == "table", "Action must be a table")
assert(action.type, "Action must have an action type")
- assert(not isServer, "Store:get() must be called from the client")
+ assert(not isServer, "Store.get() must be called from the client")
local uid: string = Util.randomString(8)
@@ -281,7 +259,7 @@ function Store.get(self: StoreType, action: Types.Action): Types.Action)
res = result
@@ -315,18 +293,14 @@ function Store.get(self: StoreType, action: Types.Action): Types.Action
end
-function Store._callSubscribers(
- self: StoreType,
- action: Types.Action,
- safeCall: boolean?
-)
+function Store._callSubscribers(action: Types.Action, safeCall: boolean?)
if not action.type or action.method == "get" or action.method == "get_result" then
-- Ignore get method actions, these
- -- will be processed by store:get()
+ -- will be processed by store.get()
return
end
- for _, callback in pairs(self._subscribers) do
+ for _, callback in pairs(_subscribers) do
if safeCall then
pcall(callback, action)
else
@@ -346,7 +320,6 @@ end
@return Types.SubscriptionId
]=]
function Store.subscribe(
- self: StoreType,
callback: (action: Types.Action) -> ()
): Types.SubscriptionId
assert(
@@ -356,71 +329,110 @@ function Store.subscribe(
local subscriptionId: Types.SubscriptionId = Util.randomString(8) -- Unique reference ID used for unsubscribing
- if Util.tableLength(self._subscribers) == 0 then
+ if Util.tableLength(_subscribers) == 0 then
-- Hook connections to start receiving events
if isServer then
- -- store:dispatch() called from client
- self._connections[#self._connections + 1] = remotes.Client.OnServerEvent:Connect(
+ -- store.dispatch() called from client
+ _connections[#_connections + 1] = remotes.Client.OnServerEvent:Connect(
function(player: Player, action: Types.Action)
action.player = player
- self:_callSubscribers(action, true)
+ Store._callSubscribers(action, true)
end
)
else
- -- store:dispatch() called from server
- self._connections[#self._connections + 1] = remotes.Client.OnClientEvent:Connect(
+ -- store.dispatch() called from server
+ _connections[#_connections + 1] = remotes.Client.OnClientEvent:Connect(
function(action: Types.Action)
- self:_callSubscribers(action, false)
+ Store._callSubscribers(action, false)
end
)
end
end
- self._subscribers[subscriptionId] = callback
+ _subscribers[subscriptionId] = callback
return subscriptionId
end
--[=[
- Unsubscribes a listener from the store using the subscription id returned from ```Store:subscribe()```.
+ Subscribes to specific action types
+
+ ```lua
+ store.subscribeTo("HELLO_WORLD", function(action: red.Action)
+ print(action.type)
+ end)
+ ```
+
+ ```lua
+ store.subscribeTo({ "HELLO_WORLD", "PING" }, function(action: red.Action)
+ print(action.type)
+ end)
+ ```
+
+ @within Store
+ @method subscribeTo
+ @param actionType { string } | string
+ @param callback (action: Types.Action) -> ()
+ @return Types.SubscriptionId
+]=]
+function Store.subscribeTo(
+ actionType: { string } | string,
+ callback: (action: Types.Action) -> ()
+)
+ if typeof(actionType) == "string" then
+ actionType = { actionType }
+ end
+
+ local actionTypes = actionType :: { string }
+
+ local subscriptionId: Types.SubscriptionId = Store.subscribe(
+ function(action: Types.Action)
+ if table.find(actionTypes, action.type) then
+ callback(action)
+ end
+ end
+ )
+
+ return subscriptionId
+end
+
+--[=[
+ Unsubscribes a listener from the store using the subscription id returned from ```Store.subscribe()```.
```lua
-- Setup the listener
local connectionId
- connectionId = store:subscribe(function(action: red.Action)
+ connectionId = store.subscribe(function(action: red.Action)
if action.type == "HELLO_WORLD" then
print(action.type)
- store:unsubscribe(connectionId) -- Stop receiving actions
+ store.unsubscribe(connectionId) -- Stop receiving actions
end
end)
task.wait(2)
- store:unsubscribe(connectionId) -- Stop receiving actions
+ store.unsubscribe(connectionId) -- Stop receiving actions
```
@within Store
@method unsubscribe
@param id Types.SubscriptionId
]=]
-function Store.unsubscribe(self: StoreType, id: Types.SubscriptionId)
- assert(
- self._subscribers[id],
- "Store:unsubscribe() - Event listener id does not exist"
- )
+function Store.unsubscribe(id: Types.SubscriptionId)
+ assert(_subscribers[id], "Store.unsubscribe() - Event listener id does not exist")
- self._subscribers[id] = nil
+ _subscribers[id] = nil
- if Util.tableLength(self._subscribers) == 0 then
+ if Util.tableLength(_subscribers) == 0 then
-- Disconnect all connections to prevent
-- unneeded events from being received
- for index: number, connection: RBXScriptConnection in pairs(self._connections) do
+ for index: number, connection: RBXScriptConnection in pairs(_connections) do
connection:Disconnect()
- self._connections[index] = nil
+ _connections[index] = nil
end
end
end
diff --git a/src/init.luau b/src/init.luau
index 814eaa1..efcb31b 100644
--- a/src/init.luau
+++ b/src/init.luau
@@ -7,15 +7,11 @@ local red = {}
red.remotes = redUtil.getRemotes()
-local Controller = require(script.Controller)
-local Server = require(script.Server)
local State = require(script.State)
local Store = require(script.Store)
-red.Server = Server
red.State = State
red.Store = Store
-red.Controller = Controller
export type SubscriptionId = string
@@ -24,11 +20,4 @@ export type Action = Types.Action
export type StateType = State.StateType
-export type ServerType = Server.ServerType
-
-export type ControllerConfig = Controller.ControllerConfig
-export type ControllerType = Controller.ControllerType
-
-export type StoreType = Store.StoreType
-
return red