Knit Documentation

repository·main·Indexed 20 days ago

https://github.com/sleitnick/knit

A lightweight framework for Roblox that simplifies server-client communication by abstracting networking infrastructure like RemoteFunctions and RemoteEvents. Knit utilizes a service-oriented architecture with server-side Services and client-side Controllers, featuring a structured lifecycle (KnitInit and KnitStart) and Promise-based startup sequence.

Tokens
10.6K
Snippets
33
Records
43
Agent score
67%

What's inside Knit

  1. What is Knit?

    main
    Knit is a lightweight framework designed to simplify communication between core parts of a Roblox experience. It provides a structured way to bridge the gap between the server and the client by providing a built-in networking layer and organizing logic into Services and Controllers.
  2. Client-accessed Services Limitation

    main
    The Intellisense optimization (direct require instead of GetService) only works for server-side services or client-side controllers. Services that must be accessed from the client via networking must still use Knit.GetService, and therefore will not benefit from the direct-require Intellisense pattern.
  3. Expose Service methods to the Client

    main

    To allow clients to call methods on a server-side Service, you must define those methods within a Client table inside the service.

    Knit automatically handles the networking by creating a RemoteFunction under the hood. Inside the Client table, you can use self.Server to reference the main service instance and call its server-side methods.

    -- Inside your Service definition on the server
    function MoneyService.Client:GetMoney(player)
    	-- 'self.Server' references the root MoneyService
    	return self.Server:GetMoney(player)
    end
  4. Understand the Knit lifecycle: KnitInit and KnitStart

    main

    Knit provides two optional lifecycle methods to help orchestrate communication between services:

    1. KnitInit: Fired after all services are created but before any service is guaranteed to be ready for consumption.

      • Use for: Setting up internal state, connecting to engine events (like Players.PlayerRemoving), or referencing other services.
      • Avoid: Calling methods or accessing events on other services here, as they may not be fully initialized.
    2. KnitStart: Fired after all KnitInit methods have completed.

      • Use for: Inter-service communication. At this stage, you can safely call methods on other services or connect to their events because all services are guaranteed to be initialized.

    Best Practice: Set up your service's core logic in KnitInit (or earlier in the ModuleScript) so that by the time KnitStart runs, the service is ready for use by others.

    -- Example of using KnitInit for cleanup
    function PointsService:KnitInit()
    	game:GetService("Players").PlayerRemoving:Connect(function(player)
    		self.PointsPerPlayer[player] = nil
    	end)
    end
  5. Use Signals for Server-to-Client and Client-to-Server communication

    main

    Knit uses signals (based on RemoteEvent) to facilitate two-way communication between the server and client. You define these signals within the Client table of a service using Knit.CreateSignal().

    Server-to-Client Events

    To notify clients of an event (e.g., a value changing), use self.Client.SignalName:Fire(player, ...) on the server. Clients can listen using :Connect().

    Client-to-Server Events

    To allow clients to trigger server logic without expecting a return value, define a signal in the Client table. The server can listen for these in KnitInit using self.Client.SignalName:Connect(function(player) ... end). Clients trigger them using :Fire().

    Unreliable Signals

    For non-critical data (like cosmetic effects) where bandwidth is more important than guaranteed delivery or order, use Knit.CreateUnreliableSignal() in the Client table. This utilizes Roblox's UnreliableRemoteEvent.

    -- Server: Creating and firing signals
    local PointsService = Knit.CreateService {
    	Name = "PointsService",
    	Client = {
    		PointsChanged = Knit.CreateSignal(),
    		GiveMePoints = Knit.CreateSignal(),
    		PlayEffect = Knit.CreateUnreliableSignal(),
    	},
    }
    
    function PointsService:AddPoints(player, amount)
    	self.Client.PointsChanged:Fire(player, newPoints)
    end
    
    -- Server: Listening to client signals in KnitInit
    function PointsService:KnitInit()
    	self.Client.GiveMePoints:Connect(function(player)
    		print(player.Name .. " requested points")
    	end)
    end
    
    -- Client: Listening and firing
    local PointsService = Knit.GetService("PointsService")
    PointsService.PointsChanged:Connect(function(points)
    	print("Points updated:", points)
    end)
    
    PointsService.GiveMePoints:Fire()
  6. How Knit middleware works

    main

    Knit uses the Comm module to provide middleware at both the inbound and outbound levels of the networking layer.

    • Inbound Middleware: Fires before a service method or signal is executed. Use this for sanitizing data, deserializing complex types, or validating permissions.
    • Outbound Middleware: Fires after a service method or signal is executed. Use this for transforming return values or serializing data before it is sent over the network.

    Middleware can be applied globally (affecting all services/controllers) or per-service (overriding global middleware for that specific service).

  7. Understand the Knit Lifecycle

    main

    Knit follows a specific execution flow to ensure all services and controllers are initialized before the application starts.

    1. Require Knit: Import the Knit module.
    2. Create Services/Controllers: Define your services (server) or controllers (client) as ModuleScripts.
    3. Call Knit.Start(): This returns a Promise that manages the startup sequence:
      • All KnitInit methods are invoked simultaneously. Knit.Start() waits for all of them to complete.
      • Once all KnitInit methods finish, all KnitStart methods are invoked simultaneously.
      • After all KnitStart methods finish, the promise returned by Knit.Start() resolves.

    Important: Services and controllers cannot be created after Knit.Start() has been called. Once created, they persist until the server shuts down or the player leaves.

    local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
    
    -- Load services or controllers here
    
    Knit.Start():catch(warn)
  8. Core concepts of Knit: Services and Controllers

    main

    Knit's architecture is centered around two primary abstractions:

    • Services: Server-side objects that hold core game logic and can expose endpoints to the client.
    • Controllers: Client-side objects that interact with Services.

    By orienting logic around these objects, developers gain cleaner organization and easier maintainability. Knit provides a built-in networking layer that allows Services to expose specific endpoints to the client through declarative code, removing the need to manually manage RemoteEvent and RemoteFunction objects.

  9. What are Controllers and how to create them

    main

    Controllers are singleton provider objects used on the client side. They act as the client-side equivalent of server-side Services. They are typically implemented as ModuleScripts.

    To create a controller, use Knit.CreateController with a required Name field. This name is the unique identifier used by other parts of your code to retrieve the controller via Knit.GetController.

    local CameraController = Knit.CreateController { Name = "CameraController" }
    
    return CameraController
  10. Use events within a Service

    main
    -- Load the Signal module and create PointsChanged signal:
    local Signal = require(Knit.Util.Signal)
    PointsService.PointsChanged = Signal.new()
    
    -- Modify AddPoints to fire the event:
    function PointsService:AddPoints(player, amount)
    	local points = self:GetPoints(player)
    	points += amount
    	self.PointsPerPlayer[player] = points
    	if amount ~= 0 then
    		self.PointsChanged:Fire(player, points)
    	end
    end
    
    -- In another service, listen to the event:
    function SomeOtherService:KnitStart()
    	local PointsService = Knit.GetService("PointsService")
    	PointsService.PointsChanged:Connect(function(player, points)
    		print("Points changed for " .. player.Name .. ":", points)
    	end)
    end
    -- Load the Signal module and create PointsChanged signal:
    local Signal = require(Knit.Util.Signal)
    PointsService.PointsChanged = Signal.new()
    
    -- Modify AddPoints to fire the event:
    function PointsService:AddPoints(player, amount)
    	local points = self:GetPoints(player)
    	points += amount
    	self.PointsPerPlayer[player] = points
    	if amount ~= 0 then
    		self.PointsChanged:Fire(player, points)
    	end
    end
  11. Use RbxUtil Loader for Custom Framework Bootstrapping

    main

    If you are building a custom framework or want a cleaner way to handle the Intellisense-friendly bootstrapping, you can use the Loader module from RbxUtil. This allows you to filter modules by name and automatically trigger startup methods like OnStart using SpawnAll.

    -- Server Startup Example
    local services = Loader.LoadDescendants(ServerScriptService, Loader.MatchesName("Service$"))
    Loader.SpawnAll(services, "OnStart")
    
    -- Client Startup Example
    local controllers = Loader.LoadDescendants(ReplicatedStorage, Loader.MatchesName("Controller$"))
    Loader.SpawnAll(controllers, "OnStart")
  12. Install Knit

    main

    Knit can be installed using either the Roblox Studio workflow or the Rojo/Wally workflow.

    Roblox Studio workflow

    1. Get Knit from the Roblox library.
    2. Place the module directly within ReplicatedStorage.

    Rojo/Wally workflow

    1. Add Knit to your wally.toml dependency list: Knit = "sleitnick/knit@^1.7"
    2. Require Knit like any other module managed by Wally.
    Knit = "sleitnick/knit@^1.7"