|
local Players = game:GetService("Players") |
|
local ReplicatedStorage = game:GetService("ReplicatedStorage") |
|
local ServerScriptService = game:GetService("ServerScriptService") |
|
|
|
local Jecs = require(ReplicatedStorage.Packages.Jecs) |
|
local Replicated = require(ServerScriptService.Server.Components.Replicated) |
|
local ReplicatedComponents = require(ReplicatedStorage.Shared.ReplicatedComponents) |
|
local ServerRemotes = require(ServerScriptService.Server.ServerRemotes) |
|
local ServerScheduler = require(ServerScriptService.Server.ServerScheduler) |
|
local World = require(ReplicatedStorage.Shared.World) |
|
local Writer = require(ReplicatedStorage.Shared.Buffer.Writer) |
|
local chain = require(ReplicatedStorage.Shared.Scheduler.chain) |
|
|
|
type ReplicatedComponentId = number |
|
type Source = Jecs.Entity |
|
type ComponentChanges = { [Source]: any } |
|
|
|
local REMOVED = {} |
|
|
|
local changes: { [ReplicatedComponentId]: ComponentChanges } = {} |
|
local deletions: { [Source]: true } = {} |
|
|
|
local addedPlayers: { [Player]: true } = {} |
|
local readyPlayers: { [Player]: true } = {} |
|
|
|
local function playerLifecycle() |
|
Players.PlayerAdded:Connect(function(player) |
|
addedPlayers[player] = true |
|
end) |
|
for _, player in Players:GetPlayers() do |
|
addedPlayers[player] = true |
|
end |
|
|
|
Players.PlayerRemoving:Connect(function(player) |
|
addedPlayers[player] = nil |
|
readyPlayers[player] = nil |
|
end) |
|
end |
|
|
|
local function setRelationshipChange( |
|
componentChanges: ComponentChanges, |
|
source: Jecs.Entity, |
|
target: Jecs.Entity, |
|
value: any |
|
) |
|
if componentChanges[source] == nil then |
|
componentChanges[source] = {} |
|
end |
|
|
|
componentChanges[source][target] = value |
|
end |
|
|
|
local function collectRelationshipChanges( |
|
world: World.World, |
|
componentChanges: ComponentChanges, |
|
replicatedComponent: ReplicatedComponents.ReplicatedComponent |
|
) |
|
local component = replicatedComponent.component |
|
local isTag = Jecs.is_tag(world, component) |
|
|
|
local function changed(entity, pair, value) |
|
if not world:has(entity, Replicated) then |
|
return |
|
end |
|
|
|
local target = Jecs.pair_second(world, pair) |
|
if not world:has(target, Replicated) then |
|
return |
|
end |
|
|
|
if isTag then |
|
setRelationshipChange(componentChanges, entity, target, true) |
|
else |
|
setRelationshipChange(componentChanges, entity, target, value) |
|
end |
|
end |
|
|
|
world:added(component, changed) |
|
world:changed(component, changed) |
|
|
|
world:removed(component, function(entity: Jecs.Entity, pair) |
|
if not world:has(entity, Replicated) then |
|
return |
|
end |
|
|
|
local target = Jecs.pair_second(world, pair) |
|
|
|
setRelationshipChange(componentChanges, entity, target, REMOVED) |
|
end) |
|
end |
|
|
|
local function addRelationshipOnReplicated( |
|
world: World.World, |
|
componentChanges: ComponentChanges, |
|
replicatedComponent: ReplicatedComponents.ReplicatedComponent, |
|
entity: Jecs.Entity |
|
) |
|
local component = replicatedComponent.component |
|
local isTag = Jecs.is_tag(world, component) |
|
|
|
-- Add relationships where entity is the target. |
|
for source, value in world:query(Jecs.pair(component, entity)):with(Replicated) do |
|
if isTag then |
|
setRelationshipChange(componentChanges, source, entity, true) |
|
else |
|
setRelationshipChange(componentChanges, source, entity, value) |
|
end |
|
end |
|
|
|
if not world:has(entity, component) then |
|
return |
|
end |
|
|
|
-- Add relationships where entity is the source. |
|
for target, value in world:targets(entity, component) do |
|
if not world:has(target, Replicated) then |
|
continue |
|
end |
|
|
|
if isTag then |
|
setRelationshipChange(componentChanges, entity, target, true) |
|
else |
|
setRelationshipChange(componentChanges, entity, target, value) |
|
end |
|
end |
|
end |
|
|
|
local function collectComponentChanges( |
|
world: World.World, |
|
componentChanges: ComponentChanges, |
|
replicatedComponent: ReplicatedComponents.ReplicatedComponent |
|
) |
|
local component = replicatedComponent.component |
|
|
|
if Jecs.is_tag(world, component) then |
|
world:added(component, function(entity) |
|
if not world:has(entity, Replicated) then |
|
return |
|
end |
|
|
|
componentChanges[entity] = true |
|
end) |
|
else |
|
local function changed(entity, _, value) |
|
if not world:has(entity, Replicated) then |
|
return |
|
end |
|
|
|
componentChanges[entity] = value |
|
end |
|
|
|
world:added(component, changed) |
|
world:changed(component, changed) |
|
end |
|
|
|
world:removed(component, function(entity: Jecs.Entity) |
|
if not world:has(entity, Replicated) then |
|
return |
|
end |
|
|
|
componentChanges[entity] = REMOVED |
|
end) |
|
end |
|
|
|
local function addComponentOnReplicated( |
|
world: World.World, |
|
componentChanges: ComponentChanges, |
|
replicatedComponent: ReplicatedComponents.ReplicatedComponent, |
|
entity: Jecs.Entity |
|
) |
|
local component = replicatedComponent.component |
|
|
|
if not world:has(entity, component) then |
|
return |
|
end |
|
|
|
if Jecs.is_tag(world, component) then |
|
componentChanges[entity] = true |
|
else |
|
componentChanges[entity] = world:get(entity, component) |
|
end |
|
end |
|
|
|
local function collectChanges(world: World.World) |
|
for index in ReplicatedComponents do |
|
changes[index] = {} |
|
end |
|
|
|
for index, replicatedComponent in ReplicatedComponents do |
|
local componentChanges = changes[index] |
|
|
|
if replicatedComponent.relationship then |
|
collectRelationshipChanges(world, componentChanges, replicatedComponent) |
|
else |
|
collectComponentChanges(world, componentChanges, replicatedComponent) |
|
end |
|
end |
|
|
|
world:added(Replicated, function(entity) |
|
for index, replicatedComponent in ReplicatedComponents do |
|
if replicatedComponent.relationship then |
|
addRelationshipOnReplicated(world, changes[index], replicatedComponent, entity) |
|
else |
|
addComponentOnReplicated(world, changes[index], replicatedComponent, entity) |
|
end |
|
end |
|
end) |
|
|
|
world:removed(Replicated, function(entity) |
|
for _, componentChanges in changes do |
|
componentChanges[entity] = nil |
|
end |
|
|
|
deletions[entity] = true |
|
end) |
|
end |
|
|
|
local function replicateChanges(world: World.World) |
|
local writer = Writer.new() |
|
|
|
if next(deletions) ~= nil then |
|
ServerRemotes.Replication.DeleteEntities.fireSet(readyPlayers, deletions :: { [number]: true }) |
|
end |
|
|
|
local values = {} |
|
local hadChanges = false |
|
for index, replicatedComponent in ReplicatedComponents do |
|
local component = replicatedComponent.component |
|
local componentChanges = changes[index] |
|
|
|
if replicatedComponent.relationship then |
|
local isTag = Jecs.is_tag(world, component) |
|
|
|
local addedCount = 0 |
|
local removalCount = 0 |
|
local added = {} |
|
local removals = {} |
|
for source, sourcePairs in componentChanges do |
|
for target, value in sourcePairs do |
|
if deletions[target] then |
|
continue |
|
end |
|
|
|
if value == REMOVED then |
|
if removals[source] == nil then |
|
removals[source] = {} |
|
end |
|
|
|
removals[source][target] = true |
|
removalCount += 1 |
|
else |
|
if added[source] == nil then |
|
added[source] = {} |
|
end |
|
|
|
if isTag then |
|
added[source][target] = true |
|
else |
|
added[source][target] = value |
|
end |
|
|
|
addedCount += 1 |
|
end |
|
end |
|
end |
|
|
|
if addedCount == 0 and removalCount == 0 then |
|
continue |
|
end |
|
|
|
hadChanges = true |
|
|
|
writer.writeu8(index - 1) |
|
|
|
writer.writeu8(removalCount) |
|
for source, targets in removals do |
|
for target in targets do |
|
writer.writef64(source) |
|
writer.writef64(target) |
|
end |
|
end |
|
|
|
writer.writeu8(addedCount) |
|
for source, targets in added do |
|
for target, value in targets do |
|
writer.writef64(source) |
|
writer.writef64(target) |
|
|
|
if not isTag then |
|
table.insert(values, value) |
|
end |
|
end |
|
end |
|
|
|
table.clear(componentChanges) |
|
else |
|
local changeCount = 0 |
|
local changed = {} |
|
local removals = {} |
|
for source, value in componentChanges do |
|
if value == REMOVED then |
|
table.insert(removals, source) |
|
else |
|
changed[source] = value |
|
changeCount += 1 |
|
end |
|
end |
|
|
|
if changeCount == 0 and #removals == 0 then |
|
continue |
|
end |
|
|
|
hadChanges = true |
|
|
|
writer.writeu8(index - 1) |
|
|
|
writer.writeu8(#removals) |
|
for _, source in removals do |
|
writer.writef64(source) |
|
end |
|
|
|
writer.writeu8(changeCount) |
|
for source, value in changed do |
|
writer.writef64(source) |
|
|
|
if not Jecs.is_tag(world, component) then |
|
table.insert(values, value) |
|
end |
|
end |
|
|
|
table.clear(componentChanges) |
|
end |
|
end |
|
|
|
table.clear(deletions) |
|
|
|
if hadChanges then |
|
ServerRemotes.Replication.UpdateComponents.fireSet(readyPlayers, writer.finish(), values) |
|
end |
|
end |
|
|
|
local function replicateInitial(world: World.World) |
|
if next(addedPlayers) == nil then |
|
return |
|
end |
|
|
|
local writer = Writer.new() |
|
local values = {} |
|
|
|
for index, replicatedComponent in ReplicatedComponents do |
|
local component = replicatedComponent.component |
|
|
|
if replicatedComponent.relationship then |
|
local isTag = Jecs.is_tag(world, component) |
|
|
|
local addedPairs = {} |
|
local count = 0 |
|
for entity in world:query(Jecs.pair(component, Jecs.Wildcard)):with(Replicated) do |
|
for target, value in world:targets(entity, component) do |
|
if world:has(target, Replicated) then |
|
count += 1 |
|
|
|
if addedPairs[entity] == nil then |
|
addedPairs[entity] = {} |
|
end |
|
|
|
if isTag then |
|
addedPairs[entity][target] = true |
|
else |
|
addedPairs[entity][target] = value |
|
end |
|
end |
|
end |
|
end |
|
|
|
if count == 0 then |
|
continue |
|
end |
|
|
|
writer.writeu8(index - 1) |
|
writer.writeu8(0) |
|
|
|
writer.writeu8(count) |
|
for source, targets in addedPairs do |
|
for target, value in targets do |
|
writer.writef64(source :: number) |
|
writer.writef64(target :: number) |
|
|
|
if not isTag then |
|
table.insert(values, value) |
|
end |
|
end |
|
end |
|
else |
|
local isTag = Jecs.is_tag(world, component) |
|
|
|
local componentValues = {} |
|
local count = 0 |
|
for id, value in world:query(component):with(Replicated) do |
|
componentValues[id] = if isTag then true else value |
|
count += 1 |
|
end |
|
|
|
if count == 0 then |
|
continue |
|
end |
|
|
|
writer.writeu8(index - 1) |
|
writer.writeu8(0) |
|
|
|
writer.writeu8(count) |
|
for id, value in componentValues do |
|
writer.writef64(id :: number) |
|
|
|
if not isTag then |
|
table.insert(values, value) |
|
end |
|
end |
|
end |
|
end |
|
|
|
ServerRemotes.Replication.UpdateComponents.fireSet(addedPlayers, writer.finish(), values) |
|
|
|
for player in addedPlayers do |
|
readyPlayers[player] = true |
|
end |
|
table.clear(addedPlayers) |
|
end |
|
|
|
local function Replication(scheduler: ServerScheduler.ServerScheduler) |
|
scheduler |
|
:addSystems("Startup", { playerLifecycle, collectChanges }) |
|
:addSystems("Last", chain({ replicateChanges, replicateInitial, ServerRemotes.sendEvents })) |
|
end |
|
|
|
return Replication |