{"id":"vocksel/matter-replication","name":"matter-replication","scope":"vocksel","platform":"roblox","description":"Mirrored from the Wally registry.","version":"0.1.1","latest":"0.1.1","versions":["0.1.0","0.1.1"],"license":"MIT","licenseRating":"safe","licenseCaveats":["License identified from the packaged LICENSE file; the manifest declared none."],"licenseVerified":true,"dependencies":{"duckarmor/freeze":{"version":"^0.1.4","alias":"Freeze"},"matter-ecs/matter":{"version":"^0.7.1","alias":"Matter"}},"integrity":"636a0bcfe1f0a5384922fc295139b7a3b29bbd12ec47a27e0878d14ebd070370","likes":0,"downloads":0,"install":"forest install vocksel/matter-replication","url":"https://forest.dev/p/roblox/vocksel/matter-replication","files":"https://api.forest.dev/ai/package/roblox/vocksel/matter-replication/files","readme":"# MatterReplication\n\n[![CI](https://github.com/vocksel/matter-replication/actions/workflows/ci.yml/badge.svg)](https://github.com/vocksel/matter-replication/actions/workflows/ci.yml)\n\nThis package exposes the building blocks necessary to create replicated entities in [Matter](https://eryn.io/matter/).\n\n## What it does\n\n1. Allows you to specify the components you want to replicate to all clients when changes occur on the server\n2. Attaches a `ServerEntity` component to all replicated entities so the client can query for server-owned entities\n\n## Installation\n\n### Wally (Recommended)\n\nMatterReplication can be installed with Wally by including it as a dependency in your `wally.toml` file.\n\n```toml\n[dependencies]\nMatterReplication = \"vocksel/matter-replication@x.x.x\"\n```\n\n### Roblox Studio\n\nDownload a copy of the rbxm from the [latest release](https://github.com/vocksel/matter-replication/releases/latest) under the Assets section, then drag and drop the file into Roblox Studio to add it to your experience.\n\n## Usage\n\nThis section walks through how the [example project](example/) uses MatterReplication to spawn server-owned parts that clients can interact with.\n\nFirst the `createReplicationSystem` function is used to create the Matter system that handles replication from the server to clients.\n\n```lua\n-- example/src/systems/replication.luau\nlocal Root = script:FindFirstAncestor(\"Example\")\n\nlocal MatterReplication = require(Root.Packages.MatterReplication)\n\nlocal Model = require(Root.components.Model)\n\nlocal REPLICATED_COMPONENTS = {\n\tModel,\n}\n\nreturn MatterReplication.createReplicationSystem(REPLICATED_COMPONENTS)\n```\n\nNext the server and client need to use the system for replication to work. The script is the same on both the server and client in this example. The differences in handling happens at the system-level. Here's the full script contents:\n\n```lua\nlocal Root = script:FindFirstAncestor(\"Example\")\n\nlocal RunService = game:GetService(\"RunService\")\n\nlocal Matter = require(Root.Packages.Matter)\n\nlocal systems = {\n\trequire(Root.systems.replication),\n\trequire(Root.systems.parts),\n}\n\nlocal world = Matter.World.new()\nlocal loop = Matter.Loop.new(world)\n\nloop:scheduleSystems(systems)\n\nloop:begin({\n\tdefault = RunService.Heartbeat,\n})\n```\n\nFinally we have the `parts` system, which handles...\n1. Spawning parts and listening for interactions on the server, and\n2. Sending interactions to the server from the client\n\nThis is a large example, but has been annotated to make it easier to understand.\n\n```lua\n-- example/src/systems/parts.luau\nlocal Root = script:FindFirstAncestor(\"Example\")\n\nlocal Players = game:GetService(\"Players\")\nlocal RunService = game:GetService(\"RunService\")\n\nlocal Matter = require(Root.Packages.Matter)\nlocal MatterReplication = require(Root.Packages.MatterReplication)\nlocal Model = require(Root.components.Model)\n\nlocal ServerEntity = MatterReplication.ServerEntity\nlocal partInteracted = Root.partInteracted\n\nlocal isServer = RunService:IsServer()\n\nlocal function newRandomColor(): Color3\n\tlocal rng = Random.new()\n\treturn Color3.new(rng:NextNumber(), rng:NextNumber(), rng:NextNumber())\nend\n\nlocal function parts(world)\n\tif isServer then\n\t\t-- Spawn a new part every 5 seconds\n\t\tif Matter.useThrottle(5) then\n\t\t\tlocal part = Instance.new(\"Part\")\n\t\t\tpart.Size = Vector3.new(4, 4, 4)\n\t\t\tpart.Position = Vector3.new(0, 15, 0)\n\t\t\tpart.Color = newRandomColor()\n\t\t\tpart.Shape = Enum.PartType.Ball\n\t\t\tpart.TopSurface = Enum.SurfaceType.Smooth\n\t\t\tpart.BottomSurface = Enum.SurfaceType.Smooth\n\t\t\tpart.Parent = workspace\n\n\t\t\t-- Defer so the server has time to replicate the Part in the first place\n\t\t\ttask.defer(function()\n\t\t\t\tworld:spawn(Model({\n\t\t\t\t\tinstance = part,\n\t\t\t\t}))\n\t\t\tend)\n\t\tend\n\n\t\t-- This handles the server reaction when a client touches one of the\n\t\t-- Parts. In this case, the color is changed to show the interaction\n\t\tfor _, player, id in Matter.useEvent(partInteracted, \"OnServerEvent\") do\n\t\t\tprint(`{player} touched entity {id}`)\n\n\t\t\tif world:contains(id) then\n\t\t\t\tlocal model = world:get(id, Model)\n\n\t\t\t\tif model then\n\t\t\t\t\tmodel.instance.Color = newRandomColor()\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\telse\n\t\tlocal character = Players.LocalPlayer.Character\n\n\t\tif character then\n\t\t\tfor _, model, serverEntity in world:query(Model, ServerEntity) do\n\t\t\t\t-- For illustrative purposes the client is the one listening for\n\t\t\t\t-- Touched events. It would be easier to do this on the server,\n\t\t\t\t-- but this is an easy way to show off user interaction causing\n\t\t\t\t-- server reaction\n\t\t\t\tfor _, other: Part in Matter.useEvent(model.instance, \"Touched\") do\n\t\t\t\t\tif other:IsDescendantOf(Players.LocalPlayer.Character) then\n\t\t\t\t\t\tpartInteracted:FireServer(serverEntity.id)\n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\tend\nend\n\nreturn parts\n```\n\nWith that, we have a complete setup for replicating server-owned entities to clients, and allowing clients to instruct the server when to make changes to those entities.\n\nYou can of course extend this example to make it possible for the client to first change the color of the part, and then instruct the server what color to make it. This can make the interaction snappier, as the client doesn't need to wait for their own interaction to be replicated from the server back to them. But that is outside the scope of this example.\n\nCheck out the source for this in the [example](example) folder which can be helpful for seeing how all the files are structured.\n\n## API\n\n**`ServerEntity: Component`**\n\nThis is a Matter component that gets automatically assigned to any entity that gets replicated.\n\nThe following example is a client-side system that uses the `ServerEntity` component to apply a `ServerEntityId` Attribute to the common `Model` component paradigm.\n\n```lua\nlocal ServerEntity = MatterReplication.ServerEntity\n\nlocal function updateEntityIdAttributes(world)\n\tfor _, model, serverEntity in world:query(Model, ServerEntity) do\n\t\tif not model.instance:GetAttribute(\"ServerEntityId\") then\n\t\t\tprint(`assigning attribute ServerEntityId={serverEntity.id} to {model.instance}`)\n\t\t\tmodel.instance:SetAttribute(\"ServerEntityId\", serverEntity.id)\n\t\tend\n\tend\nend\n```\n\n**`createReplicationSystem(replicatedComponents: { Component })`**\n\nCreates the replication system for use in your Matter loop.\n\nThe system must be included in both the server and client loops for replication to work.\n\n```lua\nlocal MatterReplication = require(ReplicatedStorage.Packages.MatterReplication)\n\nlocal Foo = require(ReplicatedStorage.Components.Foo)\nlocal Bar = require(ReplicatedStorage.Components.Bar)\nlocal Baz = require(ReplicatedStorage.Components.Baz)\n\nreturn MatterReplication.createReplicationSystem({\n\t-- The components you want to replicate go here\n\tFoo,\n\tBar,\n\tBaz\n})\n```\n\n**`resolveServerId(world: World, serverId: number): number?`**\n\nGet the client ID associated with a `ServerEntity`.\n\nThe entity IDs sent to the client from the server are typically server IDs. As such, this function can be used to resolve a server ID to the client ID for an entity.\n\nFor a non-replicated component there will not be a client ID to work with, so in those cases this function returns `nil`.\n","readmeTruncated":false}