Skip to content

Commit

Permalink
Add hot reloading support
Browse files Browse the repository at this point in the history
Closes #11
  • Loading branch information
evaera committed Jun 22, 2022
1 parent 5fd3371 commit 5f4641c
Show file tree
Hide file tree
Showing 16 changed files with 270 additions and 176 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
### Added
- Added `World:spawnAt` to spawn a new entity with a specified ID.
- Added `World:__iter` to allow iteration over all entities in the world the world from a for loop.
- Added `Loop:evictSystem(system)`, which removes a previously-scheduled system from the Loop. Evicting a system also cleans up any storage from hooks. This is intended to be used for hot reloading. Dynamically loading and unloading systems for gameplay logic is not recommended.
### Changed
- The first entity ID is now `1` instead of `0`
- Events that have no systems scheduled to run on them are no longer skipped upon calling `Loop:begin`.

## [0.2.0] - 2022-06-04
### Added
Expand Down
3 changes: 3 additions & 0 deletions example.project.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"$className": "Workspace",
"Terrain": {
"$path": "example/terrain.rbxm"
},
"Level": {
"$path": "example/level.rbxm"
}
},
"Lighting": {
Expand Down
90 changes: 4 additions & 86 deletions example/client/init.client.lua
Original file line number Diff line number Diff line change
@@ -1,89 +1,7 @@
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Packages = ReplicatedStorage.ExamplePackages
local Matter = require(ReplicatedStorage.Packages.Matter)
local Plasma = require(Packages.plasma)
local Components = require(ReplicatedStorage.Game.components)
local RemoteEvent = ReplicatedStorage:WaitForChild("MatterRemote")
local start = require(ReplicatedStorage.Game.start)
local receiveReplication = require(script.receiveReplication)

local world = Matter.World.new()
local state = {}
local loop = Matter.Loop.new(world, state)
local world, state = start(ReplicatedStorage.Game.clientSystems)

local systems = {}
for _, child in ipairs(script.systems:GetChildren()) do
if child:IsA("ModuleScript") then
table.insert(systems, require(child))
end
end

loop:scheduleSystems(systems)

local plasmaNode = Plasma.new(workspace)

loop:addMiddleware(function(nextFn)
return function()
Plasma.start(plasmaNode, nextFn)
end
end)

loop:begin({
default = RunService.Heartbeat,
RenderStepped = RunService.RenderStepped,
})

local entityIdMap = {}

RemoteEvent.OnClientEvent:Connect(function(entities)
for serverEntityId, componentMap in entities do
local clientEntityId = entityIdMap[serverEntityId]

if clientEntityId and next(componentMap) == nil then
world:despawn(clientEntityId)
print(string.format("Despawn %ds%d", clientEntityId, serverEntityId))
continue
end

local componentsToInsert = {}
local componentsToRemove = {}

local insertNames = {}
local removeNames = {}

for name, container in componentMap do
if container.data then
table.insert(componentsToInsert, Components[name](container.data))
table.insert(insertNames, name)
else
table.insert(componentsToRemove, Components[name])
table.insert(removeNames, name)
end
end

if clientEntityId == nil then
clientEntityId = world:spawn(unpack(componentsToInsert))

entityIdMap[serverEntityId] = clientEntityId

print(string.format("Spawn %ds%d with %s", clientEntityId, serverEntityId, table.concat(insertNames, ",")))
else
if #componentsToInsert > 0 then
world:insert(clientEntityId, unpack(componentsToInsert))
end

if #componentsToRemove > 0 then
world:remove(clientEntityId, unpack(componentsToRemove))
end

print(
string.format(
"Modify %ds%d adding %s, removing %s",
clientEntityId,
serverEntityId,
if #insertNames > 0 then table.concat(insertNames, ", ") else "nothing",
if #removeNames > 0 then table.concat(removeNames, ", ") else "nothing"
)
)
end
end
end)
receiveReplication(world, state)
71 changes: 71 additions & 0 deletions example/client/receiveReplication.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Game.components)
local RemoteEvent = ReplicatedStorage:WaitForChild("MatterRemote")

local function setupReplication(world, state)
local function debugPrint(...)
if state.debugEnabled then
print("Replication>", ...)
end
end

local entityIdMap = {}

RemoteEvent.OnClientEvent:Connect(function(entities)
for serverEntityId, componentMap in entities do
local clientEntityId = entityIdMap[serverEntityId]

if clientEntityId and next(componentMap) == nil then
world:despawn(clientEntityId)
debugPrint(string.format("Despawn %ds%d", clientEntityId, serverEntityId))
continue
end

local componentsToInsert = {}
local componentsToRemove = {}

local insertNames = {}
local removeNames = {}

for name, container in componentMap do
if container.data then
table.insert(componentsToInsert, Components[name](container.data))
table.insert(insertNames, name)
else
table.insert(componentsToRemove, Components[name])
table.insert(removeNames, name)
end
end

if clientEntityId == nil then
clientEntityId = world:spawn(unpack(componentsToInsert))

entityIdMap[serverEntityId] = clientEntityId

debugPrint(
string.format("Spawn %ds%d with %s", clientEntityId, serverEntityId, table.concat(insertNames, ","))
)
else
if #componentsToInsert > 0 then
world:insert(clientEntityId, unpack(componentsToInsert))
end

if #componentsToRemove > 0 then
world:remove(clientEntityId, unpack(componentsToRemove))
end

debugPrint(
string.format(
"Modify %ds%d adding %s, removing %s",
clientEntityId,
serverEntityId,
if #insertNames > 0 then table.concat(insertNames, ", ") else "nothing",
if #removeNames > 0 then table.concat(removeNames, ", ") else "nothing"
)
)
end
end
end)
end

return setupReplication
Binary file added example/level.rbxm
Binary file not shown.
69 changes: 4 additions & 65 deletions example/server/init.server.lua
Original file line number Diff line number Diff line change
@@ -1,68 +1,7 @@
local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Packages = ReplicatedStorage.ExamplePackages
local Matter = require(ReplicatedStorage.Packages.Matter)
local Plasma = require(Packages.plasma)
local Components = require(ReplicatedStorage.Game.components)
local start = require(ReplicatedStorage.Game.start)
local setupTags = require(ReplicatedStorage.Game.setupTags)

local world = Matter.World.new()
local state = {}
local loop = Matter.Loop.new(world, state)
local world = start(script.systems)

local systems = {}
for _, child in ipairs(script.systems:GetChildren()) do
if child:IsA("ModuleScript") then
table.insert(systems, require(child))
end
end

loop:scheduleSystems(systems)

local plasmaNode = Plasma.new(workspace)

loop:addMiddleware(function(nextFn)
return function()
Plasma.start(plasmaNode, nextFn)
end
end)

loop:begin({
default = RunService.Heartbeat,
RenderStepped = RunService.RenderStepped,
})

local boundTags = {
Spinner = Components.Spinner,
}

local function spawnBound(instance, component)
local id = world:spawn(
component(),
Components.Bind({
instance = instance,
}),
Components.Transform({
cframe = instance.CFrame,
})
)

instance:SetAttribute("entityId", id)
end

for tagName, component in pairs(boundTags) do
for _, instance in ipairs(CollectionService:GetTagged(tagName)) do
spawnBound(instance, component)
end

CollectionService:GetInstanceAddedSignal(tagName):Connect(function(instance)
spawnBound(instance, component)
end)

CollectionService:GetInstanceRemovedSignal(tagName):Connect(function(instance)
local id = instance:GetAttribute("entityId")
if id then
world:despawn(id)
end
end)
end
setupTags(world)
25 changes: 17 additions & 8 deletions example/server/systems/replication.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ local RemoteEvent = Instance.new("RemoteEvent")
RemoteEvent.Name = "MatterRemote"
RemoteEvent.Parent = ReplicatedStorage

local replicatedComponents = {
Components.Roomba,
Components.Model,
Components.Health,
Components.Target,
Components.Mothership,
local REPLICATED_COMPONENTS = {
"Roomba",
"Model",
"Health",
"Target",
"Mothership",
"Spinner",
}

local replicatedComponents = {}

for _, name in REPLICATED_COMPONENTS do
replicatedComponents[Components[name]] = true
end

local function replication(world)
for _, player in useEvent(Players, "PlayerAdded") do
local payload = {}
Expand All @@ -24,7 +31,9 @@ local function replication(world)
payload[tostring(entityId)] = entityPayload

for component, componentData in entityData do
entityPayload[tostring(component)] = { data = componentData }
if replicatedComponents[component] then
entityPayload[tostring(component)] = { data = componentData }
end
end
end

Expand All @@ -34,7 +43,7 @@ local function replication(world)

local changes = {}

for _, component in replicatedComponents do
for component in replicatedComponents do
for entityId, record in world:queryChanged(component) do
local key = tostring(entityId)
local name = tostring(component)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
local HttpService = game:GetService("HttpService")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Game.components)
local template = ReplicatedStorage.Assets.BillboardGui
local Matter = require(ReplicatedStorage.Packages.Matter)

warn("Press F to toggle debug overlay")

local function debugVision(world, state)
for _, input in Matter.useEvent(UserInputService, "InputBegan") do
if input.KeyCode == Enum.KeyCode.F then
state.debugEnabled = not state.debugEnabled
end
end

if not state.debugEnabled then
for id, debugLabel in world:query(Components.DebugLabel) do
debugLabel.label:Destroy()
world:remove(id, Components.DebugLabel)
end

return
end

local function rainbowRoombas(world)
for id, model in world:query(Components.Model) do
local debugLabel = world:get(id, Components.DebugLabel)

Expand Down Expand Up @@ -42,4 +60,4 @@ local function rainbowRoombas(world)
end
end

return rainbowRoombas
return debugVision
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Game.components)
local Matter = require(ReplicatedStorage.Packages.Matter)
Expand All @@ -10,6 +11,16 @@ local function roombasHurt(world)
return
end

local player = Players:GetPlayerFromCharacter(touchedModel)

if not player then
return
end

if player ~= Players.LocalPlayer then
return
end

local humanoid = touchedModel:FindFirstChildWhichIsA("Humanoid")

if not humanoid then
Expand Down
10 changes: 10 additions & 0 deletions example/shared/clientSystems/spinSpinners.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Components = require(ReplicatedStorage.Game.components)

local function spinSpinners(world)
for id, model in world:query(Components.Model, Components.Spinner) do
model.model.PrimaryPart.CFrame = model.model.PrimaryPart.CFrame * CFrame.Angles(0, math.rad(5), 0)
end
end

return spinSpinners
1 change: 1 addition & 0 deletions example/shared/components.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ local COMPONENTS = {
"Mothership",
"Lasering",
"DebugLabel",
"Spinner",
}

local components = {}
Expand Down
Loading

0 comments on commit 5f4641c

Please sign in to comment.