Skip to main content

Pulse.Remote

Resolves RemoteEvent / RemoteFunction instances by a slash-separated path under ReplicatedStorage, caches the result, and exposes clean fire / invoke helpers. Components never hold WaitForChild chains or raw Remote references.


Quick start — game's remotes.lua

-- src/misc/remotes.lua
-- Prefetch so Heartbeat handlers never block on first use.
task.spawn(function()
Pulse.Remote.prefetch(
"Remotes/Building",
"Remotes/Storage",
"Remotes/Gathering",
"CombatRemotes/Server/Attack"
)
end)

-- Named wrappers — call these from components instead of raw FireServer.
func.Remote_Build = Pulse.Remote.bind("Remotes/Building")
func.Remote_Storage = Pulse.Remote.bind("Remotes/Storage")
func.Remote_Gather = Pulse.Remote.bind("Remotes/Gathering")
func.Remote_Attack = Pulse.Remote.bind("CombatRemotes/Server/Attack")
func.Remote_GetStats = Pulse.Remote.bindi("Remotes/GetStats")
-- Inside any component
func.Remote_Build("Place", someCFrame)
local stats = func.Remote_GetStats()

API

Pulse.Remote.fire(path, ...)

Resolves the RemoteEvent at path and calls FireServer(...). Safe no-op if the path is missing.

Pulse.Remote.fire("Remotes/Gathering", targetInstance)
Pulse.Remote.fire("CombatRemotes/Server/Attack", weapon, "Hit", targetChar)

Pulse.Remote.invoke(path, ...) → any

Resolves the RemoteFunction at path and calls InvokeServer(...). Returns the result, or nil on failure.

local inventory = Pulse.Remote.invoke("Remotes/GetInventory")

Pulse.Remote.connect(path, fn) → RBXScriptConnection

Connects fn to the RemoteEvent's OnClientEvent.

Pulse.Remote.connect("Remotes/Notify", function(message)
Pulse.Notify(message)
end)

Pulse.Remote.bind(path) → fn(...)

Returns a pre-bound fire function. Call it anywhere as if calling FireServer directly.

func.Remote_Build = Pulse.Remote.bind("Remotes/Building")

-- later, in a component:
func.Remote_Build("Place", cf)
func.Remote_Build("Cancel", siteName)

Pulse.Remote.bindi(path) → fn(...) → any

Returns a pre-bound invoke function.

func.Remote_GetStats = Pulse.Remote.bindi("Remotes/GetStats")
local stats = func.Remote_GetStats()

Pulse.Remote.wrap(path) → object

Returns an object with :fire(...), :invoke(...), :connect(fn), :get(). Use when you need both fire and listen on the same remote.

local notify = Pulse.Remote.wrap("Remotes/Notify")
notify:connect(function(msg) Pulse.Notify(msg) end)
notify:fire("Hello from client") -- (unusual but possible)

Pulse.Remote.prefetch(...)

Resolves paths eagerly in background threads so Heartbeat handlers don't incur the WaitForChild latency on their first call.

task.spawn(function()
Pulse.Remote.prefetch(
"Remotes/Building",
"Remotes/Storage",
"CombatRemotes/Server/Attack"
)
end)

How paths work

Paths are resolved segment-by-segment under ReplicatedStorage using WaitForChild (10 s timeout per segment). The resolved instance is cached — every subsequent call is an O(1) table lookup.

PathResolves to
"Remotes/Building"ReplicatedStorage.Remotes.Building
"CombatRemotes/Server/Attack"ReplicatedStorage.CombatRemotes.Server.Attack
"Remotes/Nested/Deep/Remote"walks each segment in order

If any segment times out, Pulse.Remote logs a warning and all operations on that path are silent no-ops.


Design notes

  • One cache per process — all components share the same resolved instances.
  • Race-safe — if two coroutines resolve the same path simultaneously, the second waits for the first using a _PENDING sentinel.
  • Component-friendly — call Pulse.Remote.fire(...) directly inside on Heartbeat handlers with no performance concern; the resolution only yields once.