Skip to content

Commit

Permalink
Add fast paths for get with smaller component lengths (#144)
Browse files Browse the repository at this point in the history
  • Loading branch information
memorycode authored Nov 23, 2024
1 parent 775e160 commit ede11aa
Show file tree
Hide file tree
Showing 11 changed files with 108 additions and 21 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ The format is based on [Keep a Changelog][kac], and this project adheres to
- Added `Debugger.raycastParams` property.
- This allows users to customize the `RaycastParams` that is used by the mouse highlight feature. For example, a different CollisionGroup could be specified.

### Changed

- `World:get()` now has fast paths for up to four components.
- Getting one component is now more than 2x faster and this scales with more components.

## [0.9.0-beta.0] - 2024-11-15

### Added
Expand Down
5 changes: 4 additions & 1 deletion benchmark.project.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
"Matter": {
"$path": "lib"
},
"PinnedMatter": {
"Matter_0_8": {
"$path": "pinned/Matter_0_8_4.rbxm"
},
"Matter_0_9": {
"$path": "pinned/Matter_0_9_0.rbxm"
}
},
"ServerStorage": {
Expand Down
44 changes: 44 additions & 0 deletions benchmarks/get.bench.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--!optimize 2
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local Matter_0_8 = require(ReplicatedStorage.Matter_0_8)
local Matter_0_9 = require(ReplicatedStorage.Matter_0_9)

local A, B = Matter.component(), Matter.component()
local A_0_8, B_0_8 = Matter_0_8.component(), Matter_0_8.component()
local A_0_9, B_0_9 = Matter_0_9.component(), Matter_0_9.component()

local N = 1000

return {
ParameterGenerator = function()
local world, world_0_8, world_0_9 = Matter.World.new(), Matter_0_8.World.new(), Matter_0_9.World.new()
for i = 1, N do
world_0_8:spawnAt(i, A_0_8({}), B_0_8({}))
world_0_9:spawnAt(i, A_0_9({}), B_0_9({}))
world:spawnAt(i, A({ a = true }), B({ b = true }))
end

return world_0_8, world_0_9, world
end,

Functions = {
["Matter 0.8"] = function(_, world)
for i = 1, N do
world:get(i, B_0_8, A_0_8)
end
end,
["Matter 0.9"] = function(_, _, world)
for i = 1, N do
world:get(i, B_0_9, A_0_9)
end
end,
["Matter Unreleased"] = function(_, _, _, world)
for i = 1, N do
world:get(i, B, A)
end
end,
},
}
2 changes: 1 addition & 1 deletion benchmarks/insert.bench.luau
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local PinnedMatter = require(ReplicatedStorage.PinnedMatter)
local PinnedMatter = require(ReplicatedStorage.Matter_0_8)

local A, B = Matter.component(), Matter.component()
local pinnedA, pinnedB = PinnedMatter.component(), PinnedMatter.component()
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/next.bench.luau
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local PinnedMatter = require(ReplicatedStorage.PinnedMatter)
local PinnedMatter = require(ReplicatedStorage.Matter_0_8)

local world = Matter.World.new()
local pinnedWorld = PinnedMatter.World.new()
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/query.bench.luau
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local PinnedMatter = require(ReplicatedStorage.PinnedMatter)
local PinnedMatter = require(ReplicatedStorage.Matter_0_8)

local world = Matter.World.new()
local pinnedWorld = PinnedMatter.World.new()
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/stress.bench.luau
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local PinnedMatter = require(ReplicatedStorage.PinnedMatter)
local PinnedMatter = require(ReplicatedStorage.Matter_0_8)

local world = Matter.World.new()
local pinnedWorld = PinnedMatter.World.new()
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/without.bench.luau
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Matter = require(ReplicatedStorage.Matter)
local PinnedMatter = require(ReplicatedStorage.PinnedMatter)
local PinnedMatter = require(ReplicatedStorage.Matter_0_8)

local world = Matter.World.new()
local pinnedWorld = PinnedMatter.World.new()
Expand Down
63 changes: 48 additions & 15 deletions lib/World.luau
Original file line number Diff line number Diff line change
Expand Up @@ -532,37 +532,70 @@ function World:contains(id)
return self.allEntities[id] ~= nil
end

-- This function call gets inlined
local function field(entityRecord: EntityRecord, archetype: Archetype, idToIndex, component: Component)
local field = idToIndex[component.id]
if field == nil then
return nil
end

return archetype.fields[field][entityRecord.indexInArchetype]
end

--[=[
Gets a specific component (or set of components) from a specific entity in this world.
@param entityId number -- The entity ID
@param ... Component -- The components to fetch
@return ... -- Returns the component values in the same order they were passed in
]=]
function World:get(entityId, ...: Component)
function World:get(entityId: EntityId, ...: Component)
assertWorldOperationIsValid(self, entityId, ...)

local length = select("#", ...)
local componentInstances = table.create(length, nil)

local entityRecord = self.allEntities[entityId]
local archetype = entityRecord.archetype
local idToIndex = archetype.idToIndex
for i = 1, length do
local component = select(i, ...)
assertValidComponent(component, i)

-- Does this component belong to the archetype that this entity is in?
local storageIndex = idToIndex[#component]
if storageIndex == nil then
continue
local a, b, c, d = ...
local length = select("#", ...)
if length == 1 then
assertValidComponent(a, 1)

return field(entityRecord, archetype, idToIndex, a)
elseif length == 2 then
assertValidComponent(a, 1)
assertValidComponent(b, 2)

return field(entityRecord, archetype, idToIndex, a), field(entityRecord, archetype, idToIndex, b)
elseif length == 3 then
assertValidComponent(a, 1)
assertValidComponent(b, 2)
assertValidComponent(c, 3)

return field(entityRecord, archetype, idToIndex, a),
field(entityRecord, archetype, idToIndex, b),
field(entityRecord, archetype, idToIndex, c)
elseif length == 4 then
assertValidComponent(a, 1)
assertValidComponent(b, 2)
assertValidComponent(c, 3)
assertValidComponent(d, 4)

return field(entityRecord, archetype, idToIndex, a),
field(entityRecord, archetype, idToIndex, b),
field(entityRecord, archetype, idToIndex, c),
field(entityRecord, archetype, idToIndex, d)
else
local componentInstances = table.create(length, nil)
for i = 1, length do
local component = select(i, ...)
assertValidComponent(component, i)

componentInstances[i] = field(entityRecord, archetype, idToIndex, component)
end

-- Yes
componentInstances[i] = archetype.fields[storageIndex][entityRecord.indexInArchetype]
return unpack(componentInstances, 1, length)
end

return unpack(componentInstances, 1, length)
end

local function noop() end
Expand Down
2 changes: 2 additions & 0 deletions lib/component.luau
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ local function newComponent(name, defaultData)

lastId += 1
local id = lastId

component.id = id
setmetatable(component, {
__call = function(_, ...)
return component.new(...)
Expand Down
Binary file added pinned/Matter_0_9_0.rbxm
Binary file not shown.

0 comments on commit ede11aa

Please sign in to comment.