What is CommandLibrary
CommandLibrary is designed to organize complex game behaviors into modular, reusable components. It allows you to wrap your code into “Command” objects that can be scheduled, grouped, and prioritized. It is heavily inspired by the WPILib architecture used in FRC robotics.
CommandLibrary allows for modularity and easy debugging.
Commands are capable of being called from anywhere, and the scheduler automatically takes care of subsystem ownership, priority, and interruptions.
This library is designed to be used with a subsystem architecture.
Each Subsystem represents a separate system or resources, such as a weapon system. By including which subsystems a command requires, the scheduler can prevent two different pieces of logic from trying to control the same thing at the same time.
Commands themselves are cleaner than your ordinary code.
The library handles the lifecycle (Initialize, Execute, End), so you don’t have to manually manage “IsRunning” booleans or cleanup logic. When a command is interrupted or finishes, the End function runs automatically to clean up your state.
Why Did I Make This?
I made this because I had this hugggeee game idea and I was actually terrified by the sheer complexity and size of it. I got to thinking and had a revelation: to take what WPILib did, and PUT IT IN ROBLOX! Will it work? I have no idea. However, I think this library will be helpful to at least one person out there… Hopefully.
Note:
I would avoid using CommandLibrary until 1.1.0 comes out. There are a few things I need to smooth over, and I also might add decorators to allow for much simpler syntax.
CommandLibrary - Wally
CommandLibrary - Creator Store
7 Likes
Hallo! This is very very interesting. I wonder if there’re any examples or documentation, I’d love to see one.
1 Like
I included some API references in the readme, as well as a very small piece of example code. I’ll probably get around to creating some more detailed documentation soon.
2 Likes
Actually, I wouldn’t trust the example I wrote in the readme lol
Edit:
Here’s a better example
example.txt (2.4 KB)
2 Likes
I don’t know if this is a correct implementation. Let’s say I want to make a KeycardReader class
Subsystem
local Subsystem = require(CommandLibrary.Subsystem)
local ReaderSubsystem = {}
ReaderSubsystem.__index = ReaderSubsystem
function ReaderSubsystem.new(model, parent)
local self = setmetatable({}, ReaderSubsystem)
self.Subsystem = Subsystem.new("Reader:" .. model.Name)
self.Model = model
self.Primary = model.PrimaryPart
self.Parent = parent
self.Status = model:FindFirstChild("Status")
self.Sound = self.Primary:FindFirstChildOfClass("Sound")
return self
end
return ReaderSubsystem
Commands
--// ACCESS GRANTED
local Command = require(CommandLibrary.Command)
local GrantAccess = {}
GrantAccess.__index = GrantAccess
setmetatable(GrantAccess, Command)
function GrantAccess.new(reader)
local self = setmetatable(Command.new(), GrantAccess)
self.reader = reader
self:AddRequirements(reader.Subsystem)
return self
end
function GrantAccess:Initialize()
local r = self.reader
r.Sound.SoundId = "rbxassetid://" .. r.GrantedSound
r.Status.Material = Enum.Material.Neon
r.Status.Color = Color3.fromRGB(0, 255, 150)
r.Sound:Play()
end
return GrantAccess
Flow
local Sequential = CommandLibrary.SequentialCommandGroup
local WaitCommand = CommandLibrary.WaitCommand
local function AccessGrantedFlow(reader, duration)
return Sequential.new(
GrantAccess.new(reader),
WaitCommand.new(duration),
)
end
Class
local Players = game:GetService("Players")
local CommandScheduler = require(CommandLibrary.CommandScheduler)
local Reader = {}
Reader.__index = Reader
local function getPlayer(tool: Tool): Player?
local char = tool.Parent
if not char then return end
return Players:GetPlayerFromCharacter(char)
end
function Reader.new(model: Model, parent, config)
local self = setmetatable({}, Reader)
self.Subsystem = ReaderSubsystem.new(model, parent)
self.Primary = model.PrimaryPart
self.Parent = parent
self.Duration = config.Duration or 5
self:_bindTouch()
return self
end
function Reader:_bindTouch()
self.Primary.Touched:Connect(function(hit)
local tool = hit.Parent
if not tool:IsA("Tool") then return end
local player = getPlayer(tool)
if not player then return end
local perms = self.Parent:ReadPermissions()
local ok = Authenticate(player, perms)
if ok then
CommandScheduler.schedule(
AccessGrantedFlow(self.Subsystem, self.Duration)
)
end
end)
end
return Reader
Subsystem is okay as-is
Commands
local Command = require(CommandLibrary.Command)
local GrantAccess = {}
GrantAccess.__index = GrantAccess
setmetatable(GrantAccess, Command)
function GrantAccess.new(readerSubsystem, grantedSound)
local self = setmetatable(Command.new(), GrantAccess)
self.readerSubsystem = readerSubsystem
self.grantedSound = grantedSound or "rbxassetid://"
self:AddRequirements(readerSubsystem.Subsystem)
return self
end
function GrantAccess:Initialize()
local r = self.readerSubsystem
if r.Sound then
r.Sound.SoundId = self.grantedSound
r.Sound:Play()
end
if r.Status then
r.Status.Material = Enum.Material.Neon
r.Status.Color = Color3.fromRGB(0, 255, 150)
end
end
return GrantAccess
Flow
local Sequential = CommandLibrary.SequentialCommandGroup
local WaitCommand = CommandLibrary.WaitCommand
local InstantCommand = CommandLibrary.InstantCommand
local function AccessGrantedFlow(readerSubsystem, duration, grantedSound)
return Sequential.new(
GrantAccess.new(readerSubsystem, grantedSound),
WaitCommand.new(duration),
InstantCommand.new(function()
if readerSubsystem.Status then
readerSubsystem.Status.Material = Enum.Material.Plastic
readerSubsystem.Status.Color = Color3.fromRGB(255, 255, 255)
end
end)
)
end
Class
function Reader.new(model: Model, parent, config)
local self = setmetatable({}, Reader)
self.Subsystem = ReaderSubsystem.new(model, parent)
self.Primary = model.PrimaryPart
self.Parent = parent
self.Duration = config.Duration or 5
self.GrantedSound = config.GrantedSound or "rbxassetid://"
self:_bindTouch()
return self
end
function Reader:_bindTouch()
self.Primary.Touched:Connect(function(hit)
local tool = hit.Parent
if not tool:IsA("Tool") then return end
local player = getPlayer(tool)
if not player then return end
local perms = self.Parent:ReadPermissions()
local ok = Authenticate(player, perms)
if ok then
CommandScheduler.schedule(
AccessGrantedFlow(self.Subsystem, self.Duration, self.GrantedSound)
)
end
end)
end
Although, I would recommend requiring the CommandLibrary module as whole so that you don’t need to require each part you need.
1 Like
I’ll try to make a more in-depth demonstration on how to use this library. The best I can say for now is to visit the WPILib docs in order to see how they use commands.
1 Like
Anyone seeing this:
Are there any features you’d like to see? I was considering creating a networker designed specifically for this. Version 1.1.0 will bring decorators, strict typing, bug fixes, and few other minor features regarding scheduling and subsystem ownership.
1 Like
Currently I just want to see a more in-depth demonstration or documentation as I’m very new into this style of programming and yet to understand this deep enough to provide you any concrete feedbacks. Maybe just do any features you seem fit 
It is very interesting and I really like it. I’m also going to use this for my up coming project.
Perhaps you could make a Github repository for this, maybe one day I could contribute.
1 Like
Update on V1.1.0 (not released yet)
Decorators have been made, but they are around 10x less performant than a raw command. This is caused by each decorator creating a wrapper command. Instead, I will try injecting logic directly into the existing command.
Here are some current overhead statistics with raw commands:
- Running 2000 agents simultaneously costs around 51.5 µs of CPU time per frame
- CommandLib handles task allocation 2.08x faster than Roblox’s task.spawn. Task.spawn requires 1.54 µs per call to initialize a thread, while CommandLib schedules tasks in 0.74 µs
- Each command instance uses around 240 bytes
Decorators:
- A base command executes in 381 ns, and adding a 2-layer decorator chain increases that to 4.08 µs
- Although slower, if you prioritize code readability over performance, this is nice to have
- Decorators will hopefully be optimized when I release 1.1.0