
GMLK – An open-source Roblox Luau module loader and utility library
GMLK is an open source Luau framework for loading Roblox modules through Init and Start hooks. It also includes number, timestamp, duration, math, table, signal, remote, cleanup, state, pooling, timer, attribute, random…
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
GMLK is an open source Luau framework for loading Roblox modules through Init and Start hooks. It also includes number, timestamp, duration, math, table, signal, remote, cleanup, state, pooling, timer, attribute, random, and tween utilities.
Want to help improve it? Read the contribution guide.
Clone this repository and install Rojo.
Open a new Roblox Studio place. From the repository root, run rojo serve examples/coin-counter/default.project.json.
Connect the Rojo plugin in Studio, sync the project, and press Play.
Click Add coin to see the client and server modules work together. Click quickly four times to see the server rate limit a remote request.
The coin counter example uses the same GMLK source as the main project. Its project file shows where each script and module belongs.
If GMLK helps your project, a GitHub star is appreciated ⭐
Use GMLK when Use plain require when
Several client or server modules need a shared Init then Start sequence. You have a few independent modules with no startup order to coordinate.
You want one bootstrap per side and a consistent folder layout. Your current startup scripts are already simple and clear.
You need the bundled remote guards, rate limiter, and utilities together. You only need one utility and can import it directly.
GMLK does not replace Roblox's require; its loader uses require to load ModuleScripts. You can also use the utilities without creating a GMLK loader. Keep game-specific permission and purchase logic on the server.
Roblox location Purpose
ReplicatedStorage.Modules.Library.GMLK Main ModuleScript and its utility ModuleScript children
ReplicatedStorage.Modules.Client Client modules loaded by the client script
ServerStorage.Modules.Client Server modules loaded by the server script
StarterPlayer.StarterPlayerScripts.GMLKClient The single client bootstrap script
ServerScriptService.GMLKServer The single server bootstrap script
The server folder is named Client to match the requested path. Its contents run only on the server because they live in ServerStorage.
Clone this repository and sync default.project.json with Rojo. The project file creates the folders and places the two bootstrap scripts automatically. You can also copy the files into Roblox Studio by hand using the layout above. When copying by hand, make src/GMLK/init.luau the source of the GMLK ModuleScript and put the other files in src/GMLK beneath it as ModuleScript children.
Place your client ModuleScripts under ReplicatedStorage.Modules.Client and your server ModuleScripts under ServerStorage.Modules.Client. Both bootstrap scripts search descendants, so nested folders are supported. Each bootstrap runs once per client or server session.
local ReplicatedStorage = game:GetService("ReplicatedStorage") local GMLK = require(ReplicatedStorage.Modules.Library.GMLK)
local Coins = { Priority = 10 }
function Coins:Init() self.Amount = 0 self.Changed = GMLK.Signal.New() end
function Coins:Start() self.Amount += 1000 self.Changed:Fire(self.Amount) print(GMLK.Number.Abbreviate(self.Amount)) end
return Coins
Put this ModuleScript in either module folder. Lower Priority values run first; the default is 100. GMLK requires all modules, calls every Init in priority order, then calls every Start in priority order. Equal priorities are ordered by full Roblox path. Errors are written with warn and stored in Loader.Errors; other modules continue loading. Loading the same ModuleScript again with the same loader skips it.
local GMLK = require(game:GetService("ReplicatedStorage").Modules.Library.GMLK) local Loader = GMLK.New("Extra") Loader:LoadModules(workspace.ExtraModules, { IncludeDescendants = true, Debug = true })
local Coins = Loader:GetModuleByName("Coins") local Exact = Loader:GetModule(workspace.ExtraModules.Coins:GetFullName()) print(Loader.IsLoaded, #Loader.Errors, Coins, Exact)
GetModuleByName returns the first matching name. Use GetModule with a full path when names repeat.
All utility modules are children of GMLK and are available directly from the main module.
local GMLK = require(game:GetService("ReplicatedStorage").Modules.Library.GMLK)
print(GMLK.Number.Abbreviate(1250000)) print(GMLK.Number.Abbreviate(1250000, 2)) print(GMLK.Number.ParseAbbreviated("1.25M")) print(GMLK.Number.Commas(1250000))
local Timestamp = GMLK.Time.FromUTC(2026, 10, 27, 12, 30) print(GMLK.Time.FormatTimestamp(Timestamp)) print(GMLK.Time.FormatTimestamp(Timestamp, "YYYY-MM-DD", true)) print(GMLK.Time.Relative(Timestamp)) print(GMLK.Time.FormatDuration(3672))
print(GMLK.Math.Round(3.14159, 2)) print(GMLK.Math.Map(50, 0, 100, 0, 1)) print(GMLK.Math.Lerp(0, 10, 0.25)) print(GMLK.Math.Clamp01(1.5))
local Copy = GMLK.Table.DeepCopy({ Stats = { Coins = 10 } }) local Combined = GMLK.Table.Merge(Copy, { Stats = { Level = 2 } }) print(GMLK.Table.Count(Combined.Stats))
local Changed = GMLK.Signal.New() local Connection = Changed:Connect(function(Value) print(Value) end) Changed:Once(function(Value) print("First change:", Value) end) Changed:Fire(10) Connection:Disconnect() Changed:Destroy()
Rate limiting
GMLK.RateLimiter can bind directly to a RemoteEvent, UnreliableRemoteEvent, or RemoteFunction on the server. It gives each player an independent token bucket, checks the limit before calling your handler, and clears that player's bucket when they leave. Create your remotes under ReplicatedStorage.Remotes, then bind them from a server ModuleScript or Script.
local ReplicatedStorage = game:GetService("ReplicatedStorage") local GMLK = require(ReplicatedStorage.Modules.Library.GMLK) local Remotes = ReplicatedStorage:WaitForChild("Remotes")
local PurchaseLimit = GMLK.RateLimiter.New(5, 10) local PurchaseBinding = PurchaseLimit:BindEvent(Remotes:WaitForChild("Purchase"), function(Player, ItemId) print(Player.Name, ItemId) end, function(Player, RetryAfter) warn(Player.Name, "can retry in", RetryAfter, "seconds") end)
local QuoteLimit = GMLK.RateLimiter.New(2, 5) local QuoteBinding = QuoteLimit:BindFunction(Remotes:WaitForChild("GetQuote"), function(Player, ItemId) return { ItemId = ItemId, Price = 100 } end)
BindEvent(remote, handler, rejected?) drops calls over the limit and optionally calls rejected(player, retryAfter). Other OnServerEvent listeners on that remote still run, so put the protected action inside the bound handler. BindFunction(remote, handler) returns nil, retryAfter to the invoking client when blocked. It assigns OnServerInvoke, replacing any handler previously assigned outside GMLK; do not assign another handler while the binding is active. Both methods return a binding with :Disconnect() to remove the remote handler and player cleanup listener. Use a separate limiter instance for each remote if each action should have its own budget.
New(capacity, windowSeconds) requires a positive integer capacity and a positive window in seconds. The bucket permits an initial burst up to capacity and refills evenly over the window. The lower-level Allow(key, cost) method returns allowed, retryAfter, and the remaining token balance. Remaining(key), Reset(key), and ResetAll() are also available. Keys can be strings or numbers; omit the key for one shared bucket. A custom clock function can be passed as the third New argument for deterministic tests. Validate remote arguments and permissions inside each handler.
Rate limits are kept in memory for each server. Use a shared store if an action needs a limit across multiple servers.
Module Main API Use
RemoteGuard String, Number, Boolean, Instance, Optional, Check, Wrap Validate remote arguments and apply a permission callback before a handler
Cleanup New, Add, Remove, Clean, Destroy Collect connections, instances, tasks, functions, and objects with cleanup methods
StateMachine New, CanTransition, Transition, Changed, Destroy Allow only configured state changes
ObjectPool New, Acquire, Release, Destroy Reuse objects and cap how many idle objects are kept
Timer New, Start, Remaining, Stop, Destroy Run one-shot or repeating cancellable delays
AttributeObserver New, Watch, Destroy Receive new and previous attribute values
WeightedRandom New, Add, Roll, Clear Select an entry according to its weight
Tween New, Play, Cancel, CancelAll, Destroy Play one managed tween per target and cancel it cleanly
local GMLK = require(game:GetService("ReplicatedStorage").Modules.Library.GMLK)
local GuardedHandler = GMLK.RemoteGuard.Wrap({ GMLK.RemoteGuard.String(1, 32) }, function(Player, ItemId) print(Player.Name, ItemId) end, function(Player, ItemId) return Player.UserId > 0 end) local PurchaseRemote = game:GetService("ReplicatedStorage").Remotes.Purchase local PurchaseBinding = GMLK.RateLimiter.New(5, 10):BindEvent(PurchaseRemote, GuardedHandler)
local Bag = GMLK.Cleanup.New() Bag:Add(workspace.ChildAdded:Connect(function(Child) print(Child.Name) end))
local Flow = GMLK.StateMachine.New("Lobby", { Lobby = { "Playing" }, Playing = { "Lobby" }, }) Flow:Transition("Playing")
local Pool = GMLK.ObjectPool.New(function() return Instance.new("Part") end, function(Part) Part.Parent = nil end, 20) local Part = Pool:Acquire() Pool:Release(Part)
local Countdown = GMLK.Timer.New(5, function() print("Done") end) Countdown:Start()
local Observer = GMLK.AttributeObserver.New(workspace) Observer:Watch("Round", function(Current, Previous) print(Current, Previous) end, true)
local Loot = GMLK.WeightedRandom.New() Loot:Add("Common", 80):Add("Rare", 20) print(Loot:Roll())
For a LocalScript next to a Frame:
local GMLK = require(game:GetService("ReplicatedStorage").Modules.Library.GMLK) local Tweens = GMLK.Tween.New() Tweens:Play(script.Parent:WaitForChild("Frame"), TweenInfo.new(0.25), { BackgroundTransparency = 0 })
RemoteGuard.Wrap returns nil, "Invalid arguments" when input fails validation and nil, "Not allowed" when its optional permission callback returns false. Pass the wrapped handler to RateLimiter:BindEvent or :BindFunction to combine the checks. ObjectPool.New accepts create, reset, maximum idle count, and dispose callbacks in that order. Call Destroy on cleanup, observer, timer, tween, pool, and state machine objects when their owner is finished with them.
Number.Abbreviate uses K, M, B, T, Qa, Qi, Sx, Sp, Oc, No, and Dc. Number.ParseAbbreviated accepts these exact suffixes and returns nil for invalid input. Decimal places for Abbreviate and Commas are limited to 0–6. Time.FormatTimestamp accepts Roblox DateTime format patterns and uses UTC unless the third argument is true. Time.Relative uses approximate 30-day months and 365-day years. Signal:Wait() yields until the next fire and returns its arguments; destroying the signal resumes waiters with no values.
Copy and paste this prompt into an AI coding assistant that has access to your Roblox project or Rojo repository:
Migrate the Roblox project I have opened or attached to GMLK: https://github.com/Faruk-Icn/GMLK. Read GMLK's current README and source before editing. If you cannot access my project, ask me for the files or repository instead of guessing its structure.
First inspect my existing scripts, modules, startup order, require paths, remotes, and any Rojo project file. Identify which code runs on the client, which runs on the server, and which modules are shared. Preserve the game's behavior and existing public APIs. Reuse or adapt working systems instead of replacing them just to use a GMLK utility.
Install GMLK at ReplicatedStorage.Modules.Library.GMLK, with its utility ModuleScripts as children. Use one client bootstrap at StarterPlayer.StarterPlayerScripts.GMLKClient to load ReplicatedStorage.Modules.Client. Use one server bootstrap at ServerScriptService.GMLKServer to load ServerStorage.Modules.Client. The server folder really is named Client in this framework. Update the Rojo mapping if this project uses Rojo; otherwise give me exact Roblox Studio placement steps.
Move or adapt startup modules into the appropriate client or server f
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。