{"id":"coffilhg/arrangement","name":"arrangement","scope":"coffilhg","platform":"roblox","description":"Arrangement is a lightweight utility for assigning stable integer IDs to arbitrary keys and resolving them back when needed.","version":"2.0.0","latest":"2.0.0","versions":["1.0.0","2.0.0"],"license":"MPL-2.0","licenseRating":"caution","licenseCaveats":["File-level copyleft: if you modify this package's own source files, those modified files must be made available under MPL-2.0. Using it unmodified in a closed-source game is fine."],"licenseVerified":false,"dependencies":{},"integrity":"48a97fa162c3681cb9d4429ff173a68890e99c7a3d160edbbc8719d608990ba1","likes":0,"downloads":0,"install":"forest install coffilhg/arrangement","url":"https://forest.dev/p/roblox/coffilhg/arrangement","files":"https://api.forest.dev/ai/package/roblox/coffilhg/arrangement/files","readme":"# Arrangement\r\n\r\n`Arrangement` is a lightweight utility for **assigning stable integer IDs to arbitrary keys** and resolving them back when needed.\r\n\r\nAt its core, it is a deterministic, key ↔ index mapper.\r\n\r\n> index / indices / id / identifier - refer to the same concept - the return value of `Arrangement[key]`\r\n\r\n---\r\n\r\n## Available Here!\r\n- **[This repository](src/init.luau) ~ [src/init.luau](src/init.luau)**\r\n- **[Wally](<https://wally.run/package/coffilhg/arrangement>)**\r\n\r\n    ```toml\r\n    Arrangement = \"coffilhg/arrangement@2.0.0\"\r\n    ```\r\n- **[Rotriever](<https://github.com/Coffilhg/Useful-Modules/releases/tag/vArrangement/2.0.0>)**\r\n\r\n    ```toml\r\n    Arrangement = \"github.com/Coffilhg/Useful-Modules@Arrangement/2.0.0\"\r\n    ```\r\n\r\n## What It Does\r\n\r\n- Any unique key is assigned a **monotonically increasing integer**\r\n- The same key always resolves to the same index (as long as it is not removed by you)\r\n- Indices can be reversed back into their original keys\r\n- All internal state is protected from direct modification\r\n\r\nThis makes `Arrangement` suitable anywhere you want to:\r\n- Replace repeated values with compact identifiers\r\n- Cache or synchronize identifiers across systems\r\n- Decouple *what something is* from *how it is transmitted or stored*\r\n\r\n---\r\n\r\n## Example Use Case (Server ↔ Client Replication)\r\n\r\nOne (of the many possible) practical use cases is **reducing high-frequency remote traffic**.\r\n\r\nInstead of repeatedly sending long strings or table paths:\r\n\r\n```luau\r\n--- Server-side ---\r\n--onValueChangedCallback(newValue)\r\nlocal valuePath = \"Currencies.Main\" -- ~15 bytes (string) + roblox built in headers\r\nValueUpdateRemoteEvent:FireClient(player, valuePath, newValue)\r\n```\r\n\r\nBelow is one possible example flow:\r\n\r\n**Server**\r\n\r\n* Resolves a path using `Arrangement[key]`\r\n* Sends only the integer index + payload\r\n\r\n```luau\r\nlocal ValuePathArrangements = Arrangement.new()\r\n--- your code ---\r\n--onValueChangedCallback(newValue)\r\nlocal valuePath = ValuePathArrangements[\"Currencies.Main\"] -- fixed 8 bytes (number) + roblox built in headers\r\nValueUpdateRemoteEvent:FireClient(player, valuePath, newValue)\r\n```\r\n\r\n**Client**\r\n\r\n* If the index is known → resolve instantly\r\n* If unknown → request the decoded key once and cache it\r\n\r\n```luau\r\n--- Client-side ---\r\nlocal ValuePathArrangementsCache = {}\r\n\r\nValueUpdateRemoteEvent.OnClientEvent:Connect(function(valuePathIndex, newValue)\r\n    if not ValuePathArrangementsCache[valuePathIndex] then\r\n        ValuePathArrangementsCache[valuePathIndex] = GetPathArrangementRemoteFunction:InvokeServer(valuePathIndex)\r\n    end\r\n    --- your logic to apply the newValue (or abort this call, if you received same valuePathIndex again, whilst waiting for Invocation result);\r\nend)\r\n```\r\n\r\n```luau\r\n--- Server-side ---\r\nGetPathArrangementRemoteFunction.OnServerInvoke = function(player : Player, index : number)\r\n    -- add your own logic to protect yourself from misinput index (not number type, inf, NaN)\r\n    return ValuePathArrangements:GetKeyByIndex(index)\r\nend\r\n```\r\n\r\nThis turns variable-sized identifiers into **fixed-size integers** while keeping the mapping deterministic.\r\nThe same key will always resolve to the same index for the lifetime of the Arrangement instance.\r\n\r\n---\r\n\r\n## Why This Matters\r\n\r\n* Integers are smaller and cheaper to transmit than most strings\r\n* Repeated identifiers benefit the most\r\n* Lookup cost is constant and predictable\r\n\r\n---\r\n\r\n`Arrangement` does **not** enforce a specific workflow.\r\n\r\nYou could use it for anything you come up with.\r\n\r\nIf you can benefit from **\"assign once, reuse forever\" ids**, it likely fits.\r\n\r\n> **You can reuse an assigned id for as long as its key remains in the Arrangement. Removing the key invalidates its previous id**\r\n\r\n---\r\n\r\n## API\r\n\r\n### `Arrangement.new() → Arrangement`\r\n\r\nCreates a new Arrangement instance.\r\n\r\n### `Arrangement[key] → number`\r\n\r\nReturns the index for a key.\r\n\r\nCreates a new one if it doesn’t exist. (Starts with 1)\r\n\r\n`Arrangement[nil]` is an exception and always returns 0\r\n\r\n### `Arrangement:GetKeyByIndex(index) → key?`\r\n\r\nResolves an index back to its original key.\r\n\r\n`Arrangement:GetKeyByIndex(0) → nil` (since `nil` is an exception)\r\n\r\n### `Arrangement:RemoveArrangementByKey(key)`\r\n\r\nRemoves the key from the Arrangement, invalidating its previous id\r\n\r\nYou should be careful, because using `Arrangement:GetKeyByIndex` with the previously valid index will now return `nil`, because the entry was removed.\r\n\r\nMake sure nothing still references it before wiping\r\n\r\nCan be achieved by doing `Arrangement[key] = nil`\r\n\r\n**Removed ids are never reused.**\r\n\r\n### `Arrangement:RemoveArrangementByIndex(index)`\r\n\r\nRemoves the key entry associated with the given index\r\n\r\nEffectively the same as:\r\n```lua\r\nArrangement:RemoveArrangementByKey(\r\n    Arrangement:GetKeyByIndex(index)\r\n)\r\n```\r\n\r\n---\r\n\r\n## Compact test\r\n\r\n```lua\r\nlocal FruitArrangements = Arrangement.new()\r\n\r\nlocal AppleId = FruitArrangements.Apple\r\nprint(AppleId) -- 1\r\n\r\nlocal WatermelonId = FruitArrangements.Watermelon\r\nprint(WatermelonId) -- 2\r\nprint(FruitArrangements.Apple) -- 1\r\n\r\nFruitArrangements:RemoveArrangementByKey(\"Apple\") -- wipe Apple\r\nprint(FruitArrangements:GetKeyByIndex(AppleId)) -- nil (no longer there)\r\nprint(FruitArrangements.Apple) -- 3\r\n\r\nFruitArrangements.Watermelon = nil -- same as FruitArrangements:RemoveArrangementByKey(\"Watermelon\")\r\nprint(FruitArrangements.Watermelon) -- 4\r\nprint(FruitArrangements:GetKeyByIndex(4)) -- \"Watermelon\"\r\n\r\nFruitArrangements:RemoveArrangementByIndex(4)\r\nprint(FruitArrangements:GetKeyByIndex(4)) -- nil\r\n\r\n\r\n\r\n-- nil\r\nprint(FruitArrangements[nil]) -- 0\r\nprint(FruitArrangements:GetKeyByIndex(0)) -- nil\r\n\r\nFruitArrangements:RemoveArrangementByKey(nil) -- the only case when these methods do nothing\r\nFruitArrangements:RemoveArrangementByIndex(0) -- the only case when these methods do nothing\r\n\r\nprint(FruitArrangements[nil]) -- 0\r\nprint(FruitArrangements:GetKeyByIndex(0)) -- nil\r\n```\r\n\r\n---\r\n\r\n## Notes\r\n\r\n* Indexing starts at **1**\r\n* Keys are stored exactly as provided\r\n* Lifetime and synchronization strategy are intentionally left to the user\r\n* `nil` is an exception, never an entry and always returns 0\r\n    > Trying to remove it does nothing because it is never stored; it is handled as a special case\r\n    ```lua\r\n    Arrangement:RemoveArrangementByKey(nil) -- does nothing\r\n    Arrangement:RemoveArrangementByIndex(0) -- does nothing\r\n    ```\r\n* Removed ids are never reused\r\n\r\n---\r\n\r\n## License\r\n\r\nThis module is licensed under the **Mozilla Public License 2.0 (MPL-2.0)**.\r\n\r\n#### What this means for Roblox Developers:\r\n* **Use & Modify:** You can freely use this module in any public, private, or commercial Roblox game.\r\n* **File-Level Copyleft:** If you modify the source code of this module itself, you must make your modified version of the module publicly available under the MPL 2.0.\r\n* **No Viral Code Leakage:** Including this module in your game does **not** force you to open-source your other game scripts, UI layouts, or proprietary codebase. \r\n\r\nSee the full terms in the [LICENSE](LICENSE) file.\r\n\r\nCopyright © 2026 @Coffilhg (Roblox UserId 517222346)","readmeTruncated":false}