Skip to content

A helpful framework to handle core game functionality, RBXScriptSignal Connections, and help to prevent memory leaks.

Notifications You must be signed in to change notification settings

benhdev/Connect

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Connect Framework

version Twitter

A helpful framework to handle core game functionality, RBXScriptSignal Connections, and help to prevent memory leaks.

This framework is in its very early stages, I will continue to make updates regularly.

https://www.roblox.com/library/13518158092/ConnectFramework

Rojo 7.4.0-rc3.

v1.2 updates

Getting Started

To build the place from scratch, use:

rojo build -o "ConnectFramework.rbxlx"

Next, open ConnectFramework.rbxlx in Roblox Studio and start the Rojo server:

rojo serve

For more help, check out the Rojo documentation.

Usage

Require the ConnectFramework Module

local Connect = require(ReplicatedStorage:WaitForChild("ConnectFramework"))

Handling Connections

Create a new connection

Connect:create("Players.PlayerAdded", function (self, Player)
    print(Player.Name)
end)
Connect:once("Players.PlayerAdded", function (self, Player)
    print(Player.Name)
end)
Connect:parallel("Players.PlayerAdded", function (self, Player)
    print(Player.Name)
end)

Registering a connection can be done in various different ways

Note

The key argument is always optional

local connection = Connect:create(key: instance | string, signal: RBXScriptSignal | string, function (self, ...)

end)

Disconnect a connection

connection:Disconnect()

or from within the connection itself

Connect:create(key: instance | string, signal: RBXScriptSignal | string, function (self, ...)
    self:Disconnect()
end)

Tip

The following methods are also available from within the connection itself

Listen to when a connection is closed

connection:onDisconnect(function (self)

end)

Get the last Arguments passed to the connection

connection:GetArguments()

Check if the connection has errored

connection:HasError()

Get the total number of errors across all runs

connection:TotalErrors()

Monitor the execution time of the connection

Connect.tick(5, function ()
    print(connection:AverageRunTime())
end, function ()
    -- cancel when the connection is no longer present
    return not connection.Connected
end)

Tip

Example

local CollectionService = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Connect = require(ReplicatedStorage:WaitForChild("ConnectFramework"))

local signal = CollectionService:GetInstanceAddedSignal("Bread")
Connect:create(signal, function (self, instance: BasePart)
    Connect:create(instance, "Touched", function (self, hit)
        ...
        if some_condition then
            ...
            self:Disconnect()
        end
    end)
end)

Connections associated with Player.UserId automatically disconnect when the player leaves the game

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Connect = require(ReplicatedStorage:WaitForChild("ConnectFramework"))

Connect:create("Players.PlayerAdded", function (self, Player)
    Connect(Player.UserId, "RunService.Stepped", function (self, runTime, step)
        ...
    end)
end)

Connections associated with an Instance automatically disconnect when the instance is being destroyed, or when the parent is set to nil

Connect:create(Player, "CharacterAdded", function (self, Character)
    local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")
    Connect:create(HumanoidRootPart, "RunService.Stepped", function (self, runTime, step)
        print(HumanoidRootPart.Position)
    end)
end)

Handling Sessions

Creating a Session

local Session = Connect:session()

Creating a new Session Key

local key = Session:key(Player.UserId, "Points")

Retrieving a value saved in the Session

Session:get(key)
Session:find(key)
Session:fetch(key)
Session::retrieve(key)

Saving a value in the Session

Session:store(key, value)
Session:save(key, value)
Session:set(key, value)
Session:update(key, value)

Removing a value from the Session

Session:remove(key)
Session:unset(key)
Session:delete(key)

Detecting updates to any Session value

Session:onUpdate(function (self, key, value)
    print(`{key}: {value}`)
end)

Detecting updates to a specific Session value

Session:onUpdate(key, function (self, value)
    print(`{key}: {value}`)
end)

Using Events

Connect provides various event utilities which can be used to handle specific functionality in one place

Note

More functionality for events will be coming soon!

Accessing the event object

local Event = Connect:event()

Registering an Event Listener

Event:listen("action", function (arg1, arg2)
    return arg1 + arg2
end)

Dispatching an event

Event:dispatch("action", 1, 2)
Event:fire("action", 1, 2)

Tip

You may also use the .finished utility for listening to when an Event has completed.

Event:listen("action.finished", function (response)
    print(response) -- 3
end)

Data Storage & Retrieval

Connect provides various utilities to make handling datastores easier

Retrieving a value from the DataStore

local DataStoreRequest = Connect:fetch(key, function (self, response)
    Session:store(key, response)
end)

Storing a value in the DataStore

local DataStoreRequest = Connect:store(key, value, function (self, response)
    -- if working with the Session utility, the below is best practice
    -- if the value is no longer needed in the session (e.g. the Player leaving)
    Session:remove(key)
end)

Handling DataStore Errors

DataStoreRequest:onError(function (self, err)
    warn(err)

    if self.retries == 5 then
        self:CancelRetry()
    end
end)

Yield execution until a DataStoreRequest has completed

DataStoreRequest:sync()
print(DataStoreRequest.response)

Checking if the DataStoreRequest has finished

print(DataStoreRequest:finished())

Examples

init.server.lua

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Connect = require(ReplicatedStorage:WaitForChild("ConnectFramework"))

-- Create the session
local Session = Connect:session()

-- Register a global onUpdate handler
Session:onUpdate(function (self, key, value)
    print(`{key}: {value}`)
end)

-- Register the PlayerAdded Connection
local connection = Connect:create("PlayerAdded", function (self, Player)
    -- Create the leaderboard
    local Leaderstats = Instance.new("StringValue")
    Leaderstats.Name = "leaderstats"

    -- Create the points value for the leaderboard
    local Points = Instance.new("IntValue")
    Points.Name = "Points"

    -- Create a Key for the Player's points
    local key = Session:key(Player.UserId, "Points")

    -- Register a Key specific onUpdate handler
    Session:onUpdate(key, function (self, value)
        Points.Value = value
    end)

    -- Fetch the player's saved data for this key
    local DataStoreRequest = Connect:fetch(key, function (self, response)
        Connect.tick(function (i)
            -- Increase the points each second
            Session:update(key, (Session:find(key) or response or 0) + 1)
        end, function ()
            -- Cancel running if the player has left
            return not (Player and Player.Parent)
        end)
    end)

    -- Wait for the DataStoreRequest to finish
    DataStoreRequest:sync()

    Points.Parent = Leaderstats
    Leaderstats.Parent = Player
end)

-- Register the PlayerRemoving connection
Connect:create("PlayerRemoving", function (self, Player)
    local key = Session:key(Player.UserId, "Points")
    local value = Session:find(key)

    -- Save the player's points
    Connect:store(key, value, function (self, response)
        -- Remove the key from session storage, it's no longer needed
        Session:remove(key)
    end)
end)

Tip

or we can utilize events and separate the functionality into a modular styled approach

init.server.lua

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Connect = require(ReplicatedStorage:WaitForChild("ConnectFramework"))

-- Create the session
local Session = Connect:session()
local Event = Connect:event()

-- Register the PlayerAdded Connection
local connection = Connect:create("PlayerAdded", function (self, Player)
    Event:dispatch("fetch", Player)
end)

-- Register the PlayerRemoving connection
Connect:create("PlayerRemoving", function (self, Player)
    Event:dispatch("store", Player)
end)

events.server.lua

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Connect = require(ReplicatedStorage:WaitForChild("ConnectFramework"))

-- Create the session
local Session = Connect:session()
local Event = Connect:event()

Event:listen("fetch", function (Player)
    local key = Session:key(Player.UserId, "Points")
    -- Dispatch the Event which creates the leaderboard
    local Leaderstats, Points = Event:dispatch("createLeaderboard", key)
    -- Fetch the player's saved data for this key
    local DataStoreRequest = Connect:fetch(key, function (self, response)
        Connect.tick(function (i)
            -- Increase the points each second
            Session:update(key, (Session:find(key) or response or 0) + 1)
        end, function ()
            -- Cancel running if the player has left
            return not (Player and Player.Parent)
        end)
    end)

    -- Wait for the DataStoreRequest to finish
    DataStoreRequest:sync()

    Points.Parent = Leaderstats
    Leaderstats.Parent = Player
end)

Event:listen('store', function (Player)
    local key = Session:key(Player.UserId, "Points")
    local value = Session:find(key)

    -- Save the player's points
    Connect:store(key, value, function (self, response)
        -- Remove the key from session storage, it's no longer needed
        Session:remove(key)
    end)
end)

Event:listen("createLeaderboard", function (key)
    -- Create the leaderboard
    local Leaderstats = Instance.new("StringValue")
    Leaderstats.Name = "leaderstats"

    -- Create the points value for the leaderboard
    local Points = Instance.new("IntValue")
    Points.Name = "Points"

    -- Register a Key specific onUpdate handler
    Session:onUpdate(key, function (self, value)
        Points.Value = value
    end)

    return Leaderstats, Points
end)

Error Handling

Available methods on the self object within callbacks for Connect, onError, onRetryError, and onDisconnect

Disconnect the callback from the event

self:Disconnect(): void

Get the arguments of the function (excluding self)

self:GetArguments(): Tuple

Boolean of true/false if the function is currently Retrying via a call to self:ScheduleRetry()

self:IsRetrying(): boolean

The amount of times the function has began

self:CurrentCycle(): number

The amount of times the function has finished

self:CompletedCycles(): number

Boolean of true/false if the function has ever errored

self:HasError(): boolean

The total amount of errors that appeared during execution

self:TotalErrors(): number

The last error that appeared during execution

self:LastError(): string

The average execution time of the function

self:AverageRunTime(): number

Available methods on the self object within the callback for onError

Schedule the original function to retry with the same arguments

self:ScheduleRetry(delay: number?): void

Caution

Connections to events using Connect(signal, ...) will not run within Scheduled Retries to prevent duplicates. This means if the event callback failed to establish the connection on the first attempt, subsequent retries will not connect the event.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Connect = require(ReplicatedStorage:WaitForChild("ConnectFramework"))

local Players = game:GetService("Players")

local PlayerAdded = Connect(Players.PlayerAdded, function (self, Player)

    if not self:IsRetrying() then
        thisWillError()
    end

    -- In this scenario, the Player.CharacterAdded connection will never be established
    -- as this part of the code will only run in the Scheduled Retry
    local CharacterAdded = Connect(Player.UserId, Player.CharacterAdded, function (self, Character)
        print("adding character")
    end)

    print(CharacterAdded) -- output: {}
end)

PlayerAdded:onError(function (self, err)
    self:ScheduleRetry()
end)

Instead of the above, you should define all essential connections first

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Connect = require(ReplicatedStorage:WaitForChild("ConnectFramework"))

local Players = game:GetService("Players")

local PlayerAdded = Connect(Players.PlayerAdded, function (self, Player)
    -- This will only establish 1 CharacterAdded connection per Player
    local CharacterAdded = Connect(Player.UserId, Player.CharacterAdded, function (self, Character)
        print("adding character")
    end)

    print(CharacterAdded) -- output: { ... } or an empty table if in the scheduled retry

    if not self:IsRetrying() then
        thisWillError()
    end
end)

PlayerAdded:onError(function (self, err)
    self:ScheduleRetry()
end)

Note

It is possible to bypass this security measure by creating a new thread, although it is not advisable.

Core Gameplay Loops

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Connect = require(ReplicatedStorage:WaitForChild("ConnectFramework"))

local options = {
    -- Default: false
    StartInstantly = true;

    -- Default: 60
    Interval = function ()
        local RandomGenerator = Random.new(os.time())
        return RandomGenerator:NextInteger(15, 30)
    end;

    -- Default: No arguments
    Arguments = function ()
        local RandomGenerator = Random.new(os.time())
        return RandomGenerator:NextInteger(5, 10), RandomGenerator:NextInteger(100, 200)
    end;
}

Connect:CreateCoreLoop(options, function (random1, random2)
    print(random1, random2)
end)

Threads

Connect:Delay() Automatically cancels any existing thread scheduled with the specified key

if Power == "Double_Speed" then
    local PreviousWalkSpeed = Player:GetAttribute("WalkSpeed")
    Humanoid.WalkSpeed = PreviousWalkSpeed * 2

    Connect:Delay(30, Player.UserId .. "ResetWalkSpeed", function ()
        Humanoid.WalkSpeed = PreviousWalkSpeed
    end)
end

For more control of how threads get cancelled, you may use the following:

if Power == "Double_Speed" then
    local key = Player.UserId .. "ResetWalkSpeed"

    local existingThread = Connect:Thread(key)
    if existingThread then
        task.cancel(existingThread)
    end

    local PreviousWalkSpeed = Player:GetAttribute("WalkSpeed")
    Humanoid.WalkSpeed = PreviousWalkSpeed * 2

    local newThread = task.delay(30, function ()
        Humanoid.WalkSpeed = PreviousWalkSpeed
    end)

    Connect:Thread(key, newThread)
end

Debugging

Show warnings relevant to the execution of certain callbacks

Connect:DebugEnabled(true)

Show all warnings and logs, including internal framework info

Connect:DebugEnabled("internal")

Show how many server or client connections you have every 5 seconds depending on the location of the executing script

Connect:Counter()

About

A helpful framework to handle core game functionality, RBXScriptSignal Connections, and help to prevent memory leaks.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages