Skip to content
GitHub

Historical 0.4.0 guide

Riptide is a lightweight, strictly-typed, and modular Roblox framework built for Wally. It features phased initialization, safe dependency injection, a robust unified networking layer, and a shared ComponentService for managing tagged instances.

Add Riptide to your wally.toml:

[dependencies]
Riptide = "thereplicatedfirst/riptide@^0.4.0"

Download Riptide.rbxm from the v0.4.0 release and insert it as ReplicatedStorage/Packages/Riptide.

Riptide does not start automatically. You must launch the framework from your own Server and Client entry points.

local Riptide = require(ReplicatedStorage.Packages.Riptide)
local MyServerModules = ServerScriptService:WaitForChild("MyServerModules")
local MyComponents = ReplicatedStorage:WaitForChild("Components") -- optional
Riptide.Server.Launch({
ModulesFolder = MyServerModules,
ComponentsFolder = MyComponents, -- optional
})
local Riptide = require(ReplicatedStorage.Packages.Riptide)
local MyClientModules = ReplicatedStorage:WaitForChild("MyClientModules")
local MyComponents = ReplicatedStorage:WaitForChild("Components") -- optional
Riptide.Client.Launch({
ModulesFolder = MyClientModules,
ComponentsFolder = MyComponents, -- optional
})

🚀 Module Lifecycle & Dependency Injection (DI)

Section titled “🚀 Module Lifecycle & Dependency Injection (DI)”

Riptide completely eliminates the need for require() circles. Any ModuleScript inside your designated ModulesFolder will be automatically loaded into the Riptide Registry.

[!NOTE] Services and Controllers are registered by canonical module ID (relative path from ModulesFolder, e.g. Economy/PlayerData). Short names are still supported as aliases when unique.

If two modules share the same short name, Riptide marks that alias as ambiguous and requires full canonical path lookups.

Examples:

  • Riptide.GetService("Economy/PlayerData") ✅ always deterministic
  • Riptide.GetService("PlayerData") ✅ only if alias is unique
  • Riptide.GetService("Data") ⚠️ returns nil when alias is ambiguous

Methods are executed in strict phases:

  1. Init(Riptide): Called synchronously. Use this to GetService or GetController and set up your variables.
  2. Start(Riptide): Called asynchronously via task.spawn. All modules are fully initialized at this point, so it is safe to interact with them and run game logic.
--!strict
local RiptidePkg = require(ReplicatedStorage.Packages.Riptide)
type Riptide = RiptidePkg.Riptide
local PlayerState = {}
function PlayerState:Init(Riptide: Riptide)
-- Easily inject other modules
self.DataService = Riptide.GetService("DataService")
-- Listen to the unified Network layer
Riptide.Network.Register("PlayerJumped", function(player, height)
print(player.Name .. " jumped " .. height .. " studs!")
end)
end
function PlayerState:Start(Riptide: Riptide)
self.DataService:GiveMoney(100)
end
return PlayerState

Riptide automatically creates a single RemoteEvent and RemoteFunction inside its own package under the hood. No ReplicatedStorage clutter!

As of v0.4.0, network event dispatch uses a reusable trampoline handler in the hot-path to reduce closure allocations during heavy event traffic.

Client-Side API

  • Network.Register(name, callback): Listen for server events.
  • Network.Unregister(name, callback): Remove a previously registered handler.
  • Network.FireServer(name, ...): Send event data to the server.
  • Network.InvokeServer(name, ...): Request data from the server.

Server-Side API

  • Network.Register(name, callback): Listen for client events. Callback automatically receives player as the first argument.
  • Network.Unregister(name, callback): Remove a previously registered handler.
  • Network.FireClient(player, name, ...): Send event data to a specific player.
  • Network.FireAllClients(name, ...): Broadcast event data to everyone.
  • Network.InvokeClient(player, name, ...): Request data from a client.

🧩 ComponentService (Riptide.ComponentService)

Section titled “🧩 ComponentService (Riptide.ComponentService)”

A shared (server & client) system for managing component objects linked to tagged Instances via CollectionService.

As of v0.4.0, ComponentService startup is idempotent: repeated _start(...) calls are ignored to prevent duplicated CollectionService listeners.

Each Component is a ModuleScript whose name matches the tag. It must expose a new(instance) constructor and optionally a Destroy(self) cleanup method.

local Lava = {}
Lava.__index = Lava
function Lava.new(instance: BasePart)
local self = setmetatable({
_instance = instance,
_connection = nil :: RBXScriptConnection?,
}, Lava)
self._connection = instance.Touched:Connect(function(hit)
local humanoid = hit.Parent and hit.Parent:FindFirstChild("Humanoid")
if humanoid then
(humanoid :: Humanoid):TakeDamage(10)
end
end)
return self
end
function Lava:Destroy()
if self._connection then
self._connection:Disconnect()
self._connection = nil
end
end
return Lava

API

  • ComponentService:Get(instance): Get the first component attached to an instance.
  • ComponentService:Get(instance, tagName): Get a specific component by tag name.

This project is licensed under the MIT License - see the LICENSE file for details.