Reorganize repo

This commit is contained in:
Ukendio 2025-03-27 02:33:38 +01:00
parent 58e67eda0d
commit 9a22b38bb3
54 changed files with 6952 additions and 1716 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
*.luau text eol=lf

View file

@ -1,22 +1,25 @@
--- ---
name: Bug report name: Bug report
about: File a bug report for any behavior that you believe is unintentional or problematic about: File a bug report for any behavior that you believe is unintentional or problematic
title: "[BUG]" title: ""
labels: bug labels: bug
assignees: '' assignees: ""
---
---
## Describe the bug
## Describe the bug
Put a clear and concise description of what the bug is. This should be short and to the point, not to exceed more than a paragraph. Put the details inside your reproduction steps. Put a clear and concise description of what the bug is. This should be short and to the point, not to exceed more than a paragraph. Put the details inside your reproduction steps.
## Reproduction ## Reproduction
Make an easy-to-follow guide on how to reproduce it. Does it happen all the time? Will specific features affect reproduction? All these questions should be answered for a good issue.
Make an easy-to-follow guide on how to reproduce it. Does it happen all the time? Will specific features affect reproduction? All these questions should be answered for a good issue.
This is a good place to put rbxl files or scripts that help explain your reproduction steps.
This is a good place to put rbxl files or scripts that help explain your reproduction steps.
## Expected Behavior
What you expect to happen ## Expected Behavior
## Actual Behavior What you expect to happen
What actually happens
## Actual Behavior
What actually happens

View file

@ -1,14 +1,15 @@
--- ---
name: Documentation name: Documentation
about: Open an issue to add, change, or otherwise modify any part of the documentation. about: Open an issue to add, change, or otherwise modify any part of the documentation.
title: "[DOCS]" title: ""
labels: documentation labels: documentation
assignees: '' assignees: ""
--- ---
## Which Sections Does This Issue Cover? ## Which Sections Does This Issue Cover?
[Put sections (e.g. Query Concepts), page links, etc as necessary] [Put sections (e.g. Query Concepts), page links, etc as necessary]
## What Needs To Change? ## What Needs To Change?
What specifically needs to change and what suggestions do you have to change it?
What specifically needs to change and what suggestions do you have to change it?

View file

@ -1,27 +1,27 @@
--- ---
name: Feature Request name: Feature Request
about: File a feature request for something you believe should be added to Jecs about: File a feature request for something you believe should be added to Jecs
title: "[FEATURE]" title: ""
labels: enhancement labels: enhancement
assignees: '' assignees: ""
---
---
## Describe your Feature
## Describe your Feature
You should explain your feature here, and the motivation for why you want it.
You should explain your feature here, and the motivation for why you want it.
## Implementation
## Implementation
Explain how you would implement your feature here. Provide relevant API examples and such here (if applicable).
Explain how you would implement your feature here. Provide relevant API examples and such here (if applicable).
## Alternatives
## Alternatives
What other alternative implementations or otherwise relevant information is important to why you decided to go with this specific implementation?
What other alternative implementations or otherwise relevant information is important to why you decided to go with this specific implementation?
## Considerations
## Considerations
Some questions that need to be answered include the following:
Some questions that need to be answered include the following:
- Will old code break in response to this feature? - Will old code break in response to this feature?
- What are the performance impacts with this feature (if any)? - What are the performance impacts with this feature (if any)?
- How is it useful to include? - How is it useful to include?

View file

@ -1,11 +1,9 @@
syntax = "All"
column_width = 120 column_width = 120
line_endings = "Unix" line_endings = "Unix"
indent_type = "Tabs" indent_type = "Tabs"
indent_width = 4 indent_width = 4
quote_style = "ForceDouble" quote_style = "AutoPreferDouble"
call_parentheses = "Always" call_parentheses = "Always"
space_after_function_names = "Never"
collapse_simple_statement = "Never" collapse_simple_statement = "Never"
syntax = "Luau"
[sort_requires]
enabled = true

View file

@ -1,5 +1,5 @@
local jecs = require("@jecs") local jecs = require("@jecs")
local mirror = require("../mirror/init") local mirror = require("@mirror")
type i53 = number type i53 = number

View file

@ -1,49 +1,49 @@
--!optimize 2 --!optimize 2
--!native --!native
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.DevPackages.Matter) local Matter = require(ReplicatedStorage.DevPackages.Matter)
local ecr = require(ReplicatedStorage.DevPackages.ecr) local ecr = require(ReplicatedStorage.DevPackages.ecr)
local jecs = require(ReplicatedStorage.Lib) local jecs = require(ReplicatedStorage.Lib)
local pair = jecs.pair local pair = jecs.pair
local ecs = jecs.World.new() local ecs = jecs.World.new()
local mirror = require(ReplicatedStorage.mirror) local mirror = require(ReplicatedStorage.mirror)
local mcs = mirror.World.new() local mcs = mirror.World.new()
local C1 = ecs:component() local C1 = ecs:component()
local C2 = ecs:entity() local C2 = ecs:entity()
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete)) ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
local C3 = ecs:entity() local C3 = ecs:entity()
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete)) ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
local C4 = ecs:entity() local C4 = ecs:entity()
ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete)) ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete))
local E1 = mcs:component() local E1 = mcs:component()
local E2 = mcs:entity() local E2 = mcs:entity()
mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete)) mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete))
local E3 = mcs:entity() local E3 = mcs:entity()
mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete)) mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete))
local E4 = mcs:entity() local E4 = mcs:entity()
mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete)) mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete))
return { return {
ParameterGenerator = function() ParameterGenerator = function()
end, end,
Functions = { Functions = {
Mirror = function() Mirror = function()
local m = mcs:entity() local m = mcs:entity()
for i = 1, 100 do for i = 1, 100 do
mcs:add(m, E3) mcs:add(m, E3)
mcs:remove(m, E3) mcs:remove(m, E3)
end end
end, end,
Jecs = function() Jecs = function()
local j = ecs:entity() local j = ecs:entity()
for i = 1, 100 do for i = 1, 100 do
ecs:add(j, C3) ecs:add(j, C3)
ecs:remove(j, C3) ecs:remove(j, C3)
end end
end, end,
}, },
} }

BIN
coverage/amber.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

1
coverage/cmd_line Normal file
View file

@ -0,0 +1 @@
genhtml coverage.out --output-directory=coverage --synthesize-missing --ignore-errors source

BIN
coverage/emerald.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

1073
coverage/gcov.css Normal file

File diff suppressed because it is too large Load diff

BIN
coverage/glass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

129
coverage/index-sort-f.html Normal file
View file

@ -0,0 +1,129 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>LCOV - coverage.out</title>
<link rel="stylesheet" type="text/css" href="gcov.css">
</head>
<body>
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<tr><td class="title">LCOV - code coverage report</td></tr>
<tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
<tr>
<td width="100%">
<table cellpadding=1 border=0 width="100%">
<tr>
<td width="10%" class="headerItem">Current view:</td>
<td width="10%" class="headerValue">top level</td>
<td width="5%"></td>
<td width="5%"></td>
<td width="5%" class="headerCovTableHead">Coverage</td>
<td width="5%" class="headerCovTableHead" title="Covered + Uncovered code">Total</td>
<td width="5%" class="headerCovTableHead" title="Exercised code only">Hit</td>
</tr>
<tr>
<td class="headerItem">Test:</td>
<td class="headerValue">coverage.out</td>
<td></td>
<td class="headerItem">Lines:</td>
<td class="headerCovTableEntryMed">80.3&nbsp;%</td>
<td class="headerCovTableEntry">3222</td>
<td class="headerCovTableEntry">2587</td>
</tr>
<tr>
<td class="headerItem">Test Date:</td>
<td class="headerValue">2025-03-27 02:15:11</td>
<td></td>
<td class="headerItem">Functions:</td>
<td class="headerCovTableEntryLo">65.3&nbsp;%</td>
<td class="headerCovTableEntry">219</td>
<td class="headerCovTableEntry">143</td>
</tr>
<tr><td><img src="glass.png" width=3 height=3 alt=""></td></tr>
</table>
</td>
</tr>
<tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
</table>
<center>
<table width="80%" cellpadding=1 cellspacing=1 border=0>
<tr>
<td width="40%"><br></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
</tr>
<tr>
<td class="tableHead" rowspan=2>Directory <span title="Click to sort table by file name" class="tableHeadSort"><a href="index.html"><img src="updown.png" width=10 height=14 alt="Sort by file name" title="Click to sort table by file name" border=0></a></span></td>
<td class="tableHead" colspan=4>Line Coverage <span title="Click to sort table by line coverage" class="tableHeadSort"><a href="index-sort-l.html"><img src="updown.png" width=10 height=14 alt="Sort by line coverage" title="Click to sort table by line coverage" border=0></a></span></td>
<td class="tableHead" colspan=3>Function Coverage <span title="Click to sort table by function coverage" class="tableHeadSort"><img src="glass.png" width=10 height=14 alt="Sort by function coverage" title="Click to sort table by function coverage" border=0></span></td>
</tr>
<tr>
<td class="tableHead" colspan=2> Rate</td>
<td class="tableHead"> Total</td>
<td class="tableHead"> Hit</td>
<td class="tableHead"> Rate</td>
<td class="tableHead"> Total</td>
<td class="tableHead"> Hit</td>
</tr>
<tr>
<td class="coverFile"><a href="jecs/C:/Users/Marcus/Documents/packages/jecs/index.html">jecs/C:/Users/Marcus/Documents/packages/jecs</a></td>
<td class="coverBar" align="center">
<table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="ruby.png" width=72 height=10 alt="71.7%"><img src="snow.png" width=28 height=10 alt="71.7%"></td></tr></table>
</td>
<td class="coverPerLo">71.7&nbsp;%</td>
<td class="coverNumDflt">1487</td>
<td class="coverNumDflt">1066</td>
<td class="coverPerLo">53.6&nbsp;%</td>
<td class="coverNumDflt">97</td>
<td class="coverNumDflt">52</td>
</tr>
<tr>
<td class="coverFile"><a href="jecs/tools/C:/Users/Marcus/Documents/packages/jecs/tools/index.html">jecs/tools/C:/Users/Marcus/Documents/packages/jecs/tools</a></td>
<td class="coverBar" align="center">
<table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="ruby.png" width=63 height=10 alt="63.0%"><img src="snow.png" width=37 height=10 alt="63.0%"></td></tr></table>
</td>
<td class="coverPerLo">63.0&nbsp;%</td>
<td class="coverNumDflt">508</td>
<td class="coverNumDflt">320</td>
<td class="coverPerLo">63.6&nbsp;%</td>
<td class="coverNumDflt">55</td>
<td class="coverNumDflt">35</td>
</tr>
<tr>
<td class="coverFile"><a href="mnt/c/Users/Marcus/Documents/packages/jecs/test/test/index.html">/mnt/c/Users/Marcus/Documents/packages/jecs/test/test</a></td>
<td class="coverBar" align="center">
<table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="emerald.png" width=98 height=10 alt="97.9%"><img src="snow.png" width=2 height=10 alt="97.9%"></td></tr></table>
</td>
<td class="coverPerHi">97.9&nbsp;%</td>
<td class="coverNumDflt">1227</td>
<td class="coverNumDflt">1201</td>
<td class="coverPerMed">83.6&nbsp;%</td>
<td class="coverNumDflt">67</td>
<td class="coverNumDflt">56</td>
</tr>
</table>
</center>
<br>
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
<tr><td class="versionInfo">Generated by: <a href="https://github.com//linux-test-project/lcov">LCOV version 2.0-1</a></td></tr>
</table>
<br>
</body>
</html>

129
coverage/index-sort-l.html Normal file
View file

@ -0,0 +1,129 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>LCOV - coverage.out</title>
<link rel="stylesheet" type="text/css" href="gcov.css">
</head>
<body>
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<tr><td class="title">LCOV - code coverage report</td></tr>
<tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
<tr>
<td width="100%">
<table cellpadding=1 border=0 width="100%">
<tr>
<td width="10%" class="headerItem">Current view:</td>
<td width="10%" class="headerValue">top level</td>
<td width="5%"></td>
<td width="5%"></td>
<td width="5%" class="headerCovTableHead">Coverage</td>
<td width="5%" class="headerCovTableHead" title="Covered + Uncovered code">Total</td>
<td width="5%" class="headerCovTableHead" title="Exercised code only">Hit</td>
</tr>
<tr>
<td class="headerItem">Test:</td>
<td class="headerValue">coverage.out</td>
<td></td>
<td class="headerItem">Lines:</td>
<td class="headerCovTableEntryMed">80.3&nbsp;%</td>
<td class="headerCovTableEntry">3222</td>
<td class="headerCovTableEntry">2587</td>
</tr>
<tr>
<td class="headerItem">Test Date:</td>
<td class="headerValue">2025-03-27 02:15:11</td>
<td></td>
<td class="headerItem">Functions:</td>
<td class="headerCovTableEntryLo">65.3&nbsp;%</td>
<td class="headerCovTableEntry">219</td>
<td class="headerCovTableEntry">143</td>
</tr>
<tr><td><img src="glass.png" width=3 height=3 alt=""></td></tr>
</table>
</td>
</tr>
<tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
</table>
<center>
<table width="80%" cellpadding=1 cellspacing=1 border=0>
<tr>
<td width="40%"><br></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
</tr>
<tr>
<td class="tableHead" rowspan=2>Directory <span title="Click to sort table by file name" class="tableHeadSort"><a href="index.html"><img src="updown.png" width=10 height=14 alt="Sort by file name" title="Click to sort table by file name" border=0></a></span></td>
<td class="tableHead" colspan=4>Line Coverage <span title="Click to sort table by line coverage" class="tableHeadSort"><img src="glass.png" width=10 height=14 alt="Sort by line coverage" title="Click to sort table by line coverage" border=0></span></td>
<td class="tableHead" colspan=3>Function Coverage <span title="Click to sort table by function coverage" class="tableHeadSort"><a href="index-sort-f.html"><img src="updown.png" width=10 height=14 alt="Sort by function coverage" title="Click to sort table by function coverage" border=0></a></span></td>
</tr>
<tr>
<td class="tableHead" colspan=2> Rate</td>
<td class="tableHead"> Total</td>
<td class="tableHead"> Hit</td>
<td class="tableHead"> Rate</td>
<td class="tableHead"> Total</td>
<td class="tableHead"> Hit</td>
</tr>
<tr>
<td class="coverFile"><a href="jecs/tools/C:/Users/Marcus/Documents/packages/jecs/tools/index.html">jecs/tools/C:/Users/Marcus/Documents/packages/jecs/tools</a></td>
<td class="coverBar" align="center">
<table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="ruby.png" width=63 height=10 alt="63.0%"><img src="snow.png" width=37 height=10 alt="63.0%"></td></tr></table>
</td>
<td class="coverPerLo">63.0&nbsp;%</td>
<td class="coverNumDflt">508</td>
<td class="coverNumDflt">320</td>
<td class="coverPerLo">63.6&nbsp;%</td>
<td class="coverNumDflt">55</td>
<td class="coverNumDflt">35</td>
</tr>
<tr>
<td class="coverFile"><a href="jecs/C:/Users/Marcus/Documents/packages/jecs/index.html">jecs/C:/Users/Marcus/Documents/packages/jecs</a></td>
<td class="coverBar" align="center">
<table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="ruby.png" width=72 height=10 alt="71.7%"><img src="snow.png" width=28 height=10 alt="71.7%"></td></tr></table>
</td>
<td class="coverPerLo">71.7&nbsp;%</td>
<td class="coverNumDflt">1487</td>
<td class="coverNumDflt">1066</td>
<td class="coverPerLo">53.6&nbsp;%</td>
<td class="coverNumDflt">97</td>
<td class="coverNumDflt">52</td>
</tr>
<tr>
<td class="coverFile"><a href="mnt/c/Users/Marcus/Documents/packages/jecs/test/test/index.html">/mnt/c/Users/Marcus/Documents/packages/jecs/test/test</a></td>
<td class="coverBar" align="center">
<table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="emerald.png" width=98 height=10 alt="97.9%"><img src="snow.png" width=2 height=10 alt="97.9%"></td></tr></table>
</td>
<td class="coverPerHi">97.9&nbsp;%</td>
<td class="coverNumDflt">1227</td>
<td class="coverNumDflt">1201</td>
<td class="coverPerMed">83.6&nbsp;%</td>
<td class="coverNumDflt">67</td>
<td class="coverNumDflt">56</td>
</tr>
</table>
</center>
<br>
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
<tr><td class="versionInfo">Generated by: <a href="https://github.com//linux-test-project/lcov">LCOV version 2.0-1</a></td></tr>
</table>
<br>
</body>
</html>

129
coverage/index.html Normal file
View file

@ -0,0 +1,129 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>LCOV - coverage.out</title>
<link rel="stylesheet" type="text/css" href="gcov.css">
</head>
<body>
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<tr><td class="title">LCOV - code coverage report</td></tr>
<tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
<tr>
<td width="100%">
<table cellpadding=1 border=0 width="100%">
<tr>
<td width="10%" class="headerItem">Current view:</td>
<td width="10%" class="headerValue">top level</td>
<td width="5%"></td>
<td width="5%"></td>
<td width="5%" class="headerCovTableHead">Coverage</td>
<td width="5%" class="headerCovTableHead" title="Covered + Uncovered code">Total</td>
<td width="5%" class="headerCovTableHead" title="Exercised code only">Hit</td>
</tr>
<tr>
<td class="headerItem">Test:</td>
<td class="headerValue">coverage.out</td>
<td></td>
<td class="headerItem">Lines:</td>
<td class="headerCovTableEntryMed">80.3&nbsp;%</td>
<td class="headerCovTableEntry">3222</td>
<td class="headerCovTableEntry">2587</td>
</tr>
<tr>
<td class="headerItem">Test Date:</td>
<td class="headerValue">2025-03-27 02:15:11</td>
<td></td>
<td class="headerItem">Functions:</td>
<td class="headerCovTableEntryLo">65.3&nbsp;%</td>
<td class="headerCovTableEntry">219</td>
<td class="headerCovTableEntry">143</td>
</tr>
<tr><td><img src="glass.png" width=3 height=3 alt=""></td></tr>
</table>
</td>
</tr>
<tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
</table>
<center>
<table width="80%" cellpadding=1 cellspacing=1 border=0>
<tr>
<td width="40%"><br></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
<td width="8%"></td>
</tr>
<tr>
<td class="tableHead" rowspan=2>Directory <span title="Click to sort table by file name" class="tableHeadSort"><img src="glass.png" width=10 height=14 alt="Sort by file name" title="Click to sort table by file name" border=0></span></td>
<td class="tableHead" colspan=4>Line Coverage <span title="Click to sort table by line coverage" class="tableHeadSort"><a href="index-sort-l.html"><img src="updown.png" width=10 height=14 alt="Sort by line coverage" title="Click to sort table by line coverage" border=0></a></span></td>
<td class="tableHead" colspan=3>Function Coverage <span title="Click to sort table by function coverage" class="tableHeadSort"><a href="index-sort-f.html"><img src="updown.png" width=10 height=14 alt="Sort by function coverage" title="Click to sort table by function coverage" border=0></a></span></td>
</tr>
<tr>
<td class="tableHead" colspan=2> Rate</td>
<td class="tableHead"> Total</td>
<td class="tableHead"> Hit</td>
<td class="tableHead"> Rate</td>
<td class="tableHead"> Total</td>
<td class="tableHead"> Hit</td>
</tr>
<tr>
<td class="coverFile"><a href="jecs/C:/Users/Marcus/Documents/packages/jecs/index.html">jecs/C:/Users/Marcus/Documents/packages/jecs</a></td>
<td class="coverBar" align="center">
<table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="ruby.png" width=72 height=10 alt="71.7%"><img src="snow.png" width=28 height=10 alt="71.7%"></td></tr></table>
</td>
<td class="coverPerLo">71.7&nbsp;%</td>
<td class="coverNumDflt">1487</td>
<td class="coverNumDflt">1066</td>
<td class="coverPerLo">53.6&nbsp;%</td>
<td class="coverNumDflt">97</td>
<td class="coverNumDflt">52</td>
</tr>
<tr>
<td class="coverFile"><a href="jecs/tools/C:/Users/Marcus/Documents/packages/jecs/tools/index.html">jecs/tools/C:/Users/Marcus/Documents/packages/jecs/tools</a></td>
<td class="coverBar" align="center">
<table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="ruby.png" width=63 height=10 alt="63.0%"><img src="snow.png" width=37 height=10 alt="63.0%"></td></tr></table>
</td>
<td class="coverPerLo">63.0&nbsp;%</td>
<td class="coverNumDflt">508</td>
<td class="coverNumDflt">320</td>
<td class="coverPerLo">63.6&nbsp;%</td>
<td class="coverNumDflt">55</td>
<td class="coverNumDflt">35</td>
</tr>
<tr>
<td class="coverFile"><a href="mnt/c/Users/Marcus/Documents/packages/jecs/test/test/index.html">/mnt/c/Users/Marcus/Documents/packages/jecs/test/test</a></td>
<td class="coverBar" align="center">
<table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="emerald.png" width=98 height=10 alt="97.9%"><img src="snow.png" width=2 height=10 alt="97.9%"></td></tr></table>
</td>
<td class="coverPerHi">97.9&nbsp;%</td>
<td class="coverNumDflt">1227</td>
<td class="coverNumDflt">1201</td>
<td class="coverPerMed">83.6&nbsp;%</td>
<td class="coverNumDflt">67</td>
<td class="coverNumDflt">56</td>
</tr>
</table>
</center>
<br>
<table width="100%" border=0 cellspacing=0 cellpadding=0>
<tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr>
<tr><td class="versionInfo">Generated by: <a href="https://github.com//linux-test-project/lcov">LCOV version 2.0-1</a></td></tr>
</table>
<br>
</body>
</html>

BIN
coverage/ruby.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

BIN
coverage/snow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

BIN
coverage/updown.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

BIN
demo.rbxl

Binary file not shown.

View file

@ -1,48 +1,48 @@
local events = {} local events = {}
local function trackers_invoke(event, component, entity, ...) local function trackers_invoke(event, component, entity, ...)
local trackers = events[event][component] local trackers = events[event][component]
if not trackers then if not trackers then
return return
end end
for _, tracker in trackers do for _, tracker in trackers do
tracker(entity, data) tracker(entity, data)
end end
end end
local function trackers_init(event, component, fn) local function trackers_init(event, component, fn)
local ob = events[event] local ob = events[event]
return { return {
connect = function(component, fn) connect = function(component, fn)
local trackers = ob[component] local trackers = ob[component]
if not trackers then if not trackers then
trackers = {} trackers = {}
ob[component] = trackers ob[component] = trackers
end end
table.insert(trackers, fn) table.insert(trackers, fn)
end, end,
invoke = function(component, ...) invoke = function(component, ...)
trackers_invoke(event, component, ...) trackers_invoke(event, component, ...)
end end
} }
return function(component, fn) return function(component, fn)
local trackers = ob[component] local trackers = ob[component]
if not trackers then if not trackers then
trackers = {} trackers = {}
ob[component] = trackers ob[component] = trackers
end end
table.insert(trackers, fn) table.insert(trackers, fn)
end end
end end
local trackers = { local trackers = {
emplace = trackers_init("emplace"), emplace = trackers_init("emplace"),
add = trackers_init("added"), add = trackers_init("added"),
remove = trackers_init("removed") remove = trackers_init("removed")
} }
return trackers return trackers

View file

@ -1,67 +1,67 @@
--!optimize 2 --!optimize 2
--!native --!native
--!strict --!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jecs = require(ReplicatedStorage.ecs) local jecs = require(ReplicatedStorage.ecs)
local __ = jecs.Wildcard local __ = jecs.Wildcard
local std = ReplicatedStorage.std local std = ReplicatedStorage.std
local world = require(std.world) local world = require(std.world)
local Position = world:component() :: jecs.Entity<vector> local Position = world:component() :: jecs.Entity<vector>
local Previous = jecs.Rest local Previous = jecs.Rest
local pre = jecs.pair(Position, Previous) local pre = jecs.pair(Position, Previous)
local added = world local added = world
:query(Position) :query(Position)
:without(pre) :without(pre)
:cached() :cached()
local changed = world local changed = world
:query(Position, pre) :query(Position, pre)
:cached() :cached()
local removed = world local removed = world
:query(pre) :query(pre)
:without(Position) :without(Position)
:cached() :cached()
local children = {} local children = {}
for i = 1, 10 do for i = 1, 10 do
local e = world:entity() local e = world:entity()
world:set(e, Position, vector.create(i, i, i)) world:set(e, Position, vector.create(i, i, i))
table.insert(children, e) table.insert(children, e)
end end
local function flip() local function flip()
return math.random() > 0.5 return math.random() > 0.5
end end
local function system() local function system()
for i, child in children do for i, child in children do
world:set(child, Position, vector.create(i,i,i)) world:set(child, Position, vector.create(i,i,i))
end end
for e, p in added:iter() do for e, p in added:iter() do
world:set(e, pre, p) world:set(e, pre, p)
end end
for i, child in children do for i, child in children do
if flip() then if flip() then
world:set(child, Position, vector.create(i + 1, i + 1, i + 1)) world:set(child, Position, vector.create(i + 1, i + 1, i + 1))
end end
end end
for e, new, old in changed:iter() do for e, new, old in changed:iter() do
if new ~= old then if new ~= old then
world:set(e, pre, new) world:set(e, pre, new)
end end
end end
for i, child in children do for i, child in children do
world:remove(child, Position) world:remove(child, Position)
end end
for e in removed:iter() do for e in removed:iter() do
world:remove(e, pre) world:remove(e, pre)
end end
end end
local scheduler = require(std.scheduler) local scheduler = require(std.scheduler)
scheduler.SYSTEM(system) scheduler.SYSTEM(system)
return 0 return 0

View file

@ -1,90 +1,90 @@
--!optimize 2 --!optimize 2
--!native --!native
--!strict --!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jecs = require(ReplicatedStorage.ecs) local jecs = require(ReplicatedStorage.ecs)
local __ = jecs.Wildcard local __ = jecs.Wildcard
local std = ReplicatedStorage.std local std = ReplicatedStorage.std
local world = require(std.world) local world = require(std.world)
local Position = world:component() :: jecs.Entity<vector> local Position = world:component() :: jecs.Entity<vector>
local Previous = jecs.Rest local Previous = jecs.Rest
local pre = jecs.pair(Position, Previous) local pre = jecs.pair(Position, Previous)
local added = world local added = world
:query(Position) :query(Position)
:without(pre) :without(pre)
:cached() :cached()
local changed = world local changed = world
:query(Position, pre) :query(Position, pre)
:cached() :cached()
local removed = world local removed = world
:query(pre) :query(pre)
:without(Position) :without(Position)
:cached() :cached()
local children = {} local children = {}
for i = 1, 10 do for i = 1, 10 do
local e = world:entity() local e = world:entity()
world:set(e, Position, vector.create(i, i, i)) world:set(e, Position, vector.create(i, i, i))
table.insert(children, e) table.insert(children, e)
end end
local function flip() local function flip()
return math.random() > 0.5 return math.random() > 0.5
end end
local entity_index = world.entity_index local entity_index = world.entity_index
local function copy(archetypes, id) local function copy(archetypes, id)
for _, archetype in archetypes do for _, archetype in archetypes do
local to = jecs.archetype_traverse_add(world, pre, archetype) local to = jecs.archetype_traverse_add(world, pre, archetype)
local columns = to.columns local columns = to.columns
local records = to.records local records = to.records
local old = columns[records[pre].column] local old = columns[records[pre].column]
local new = columns[records[id].column] local new = columns[records[id].column]
if to ~= archetype then if to ~= archetype then
for _, entity in archetype.entities do for _, entity in archetype.entities do
local r = jecs.entity_index_try_get_fast(entity_index, entity) local r = jecs.entity_index_try_get_fast(entity_index, entity)
jecs.entity_move(entity_index, entity, r, to) jecs.entity_move(entity_index, entity, r, to)
end end
end end
table.move(new, 1, #new, 1, old) table.move(new, 1, #new, 1, old)
end end
end end
local function system2() local function system2()
for i, child in children do for i, child in children do
world:set(child, Position, vector.create(i,i,i)) world:set(child, Position, vector.create(i,i,i))
end end
for e, p in added:iter() do for e, p in added:iter() do
end end
copy(added:archetypes(), Position) copy(added:archetypes(), Position)
for i, child in children do for i, child in children do
if flip() then if flip() then
world:set(child, Position, vector.create(i + 1, i + 1, i + 1)) world:set(child, Position, vector.create(i + 1, i + 1, i + 1))
end end
end end
for e, new, old in changed:iter() do for e, new, old in changed:iter() do
if new ~= old then if new ~= old then
end end
end end
copy(changed:archetypes(), Position) copy(changed:archetypes(), Position)
for i, child in children do for i, child in children do
world:remove(child, Position) world:remove(child, Position)
end end
for e in removed:iter() do for e in removed:iter() do
world:remove(e, pre) world:remove(e, pre)
end end
end end
local scheduler = require(std.scheduler) local scheduler = require(std.scheduler)
scheduler.SYSTEM(system2) scheduler.SYSTEM(system2)
return 0 return 0

View file

@ -52,7 +52,7 @@ export type Archetype = {
type ecs_record_t = { type ecs_record_t = {
archetype: ecs_archetype_t, archetype: ecs_archetype_t,
row: number, row: number,
dense: i24 dense: i24,
} }
type ecs_id_record_t = { type ecs_id_record_t = {
@ -90,8 +90,8 @@ type ecs_query_data_t = {
} }
type ecs_observer_t = { type ecs_observer_t = {
callback: (archetype: ecs_archetype_t) -> (), callback: (archetype: ecs_archetype_t) -> (),
query: ecs_query_data_t, query: ecs_query_data_t,
} }
type ecs_observable_t = Map<i53, Map<i53, { ecs_observer_t }>> type ecs_observable_t = Map<i53, Map<i53, { ecs_observer_t }>>
@ -295,10 +295,6 @@ local function ecs_pair_second(world: ecs_world_t, e: i53)
return ecs_get_alive(world, obj) return ecs_get_alive(world, obj)
end end
local function ecs_component_record(world: ecs_world_t, component: i53)
return world.component_index[component]
end
local function query_match(query: ecs_query_data_t, local function query_match(query: ecs_query_data_t,
archetype: ecs_archetype_t) archetype: ecs_archetype_t)
local records = archetype.records local records = archetype.records
@ -559,10 +555,10 @@ local function id_record_ensure(world: ecs_world_t, id: number): ecs_id_record_t
local target = 0 local target = 0
local is_pair = ECS_IS_PAIR(id) local is_pair = ECS_IS_PAIR(id)
if is_pair then if is_pair then
relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id)) relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id)) :: i53
assert(relation and entity_index_is_alive( assert(relation and entity_index_is_alive(
entity_index, relation), ECS_INTERNAL_ERROR) entity_index, relation), ECS_INTERNAL_ERROR)
target = entity_index_get_alive(entity_index, ECS_PAIR_SECOND(id)) target = entity_index_get_alive(entity_index, ECS_PAIR_SECOND(id)) :: i53
assert(target and entity_index_is_alive( assert(target and entity_index_is_alive(
entity_index, target), ECS_INTERNAL_ERROR) entity_index, target), ECS_INTERNAL_ERROR)
end end
@ -646,7 +642,6 @@ local function archetype_create(world: ecs_world_t, id_types: { i24 }, ty, prev:
local records: { number } = {} local records: { number } = {}
local counts: {number} = {} local counts: {number} = {}
local entity_index = world.entity_index
local archetype: ecs_archetype_t = { local archetype: ecs_archetype_t = {
columns = columns, columns = columns,
entities = {}, entities = {},

View file

@ -1,186 +0,0 @@
site_name: Jecs
site_url: jecs.github.io/jecs
repo_name: ukendio/jecs
repo_url: https://github.com/ukendio/jecs
extra:
version:
provider: mike
theme:
name: material
custom_dir: docs/assets/overrides
logo: assets/logo
favicon: assets/logo-dark.svg
palette:
- media: "(prefers-color-scheme: dark)"
scheme: fusiondoc-dark
toggle:
icon: octicons/sun-24
title: Switch to light theme
- media: "(prefers-color-scheme: light)"
scheme: fusiondoc-light
toggle:
icon: octicons/moon-24
title: Switch to dark theme
font:
text: Plus Jakarta Sans
code: JetBrains Mono
features:
- navigation.tabs
- navigation.top
- navigation.sections
- navigation.instant
- navigation.indexes
- search.suggest
- search.highlight
icon:
repo: octicons/mark-github-16
extra_css:
- assets/theme/fusiondoc.css
- assets/theme/colours.css
- assets/theme/code.css
- assets/theme/paragraph.css
- assets/theme/page.css
- assets/theme/admonition.css
- assets/theme/404.css
- assets/theme/api-reference.css
- assets/theme/dev-tools.css
extra_javascript:
- assets/scripts/smooth-scroll.js
nav:
- Home: index.md
- Tutorials:
- Get Started: tutorials/index.md
- Installing Fusion: tutorials/get-started/installing-fusion.md
- Developer Tools: tutorials/get-started/developer-tools.md
- Getting Help: tutorials/get-started/getting-help.md
- Fundamentals:
- Scopes: tutorials/fundamentals/scopes.md
- Values: tutorials/fundamentals/values.md
- Observers: tutorials/fundamentals/observers.md
- Computeds: tutorials/fundamentals/computeds.md
- Tables:
- ForValues: tutorials/tables/forvalues.md
- ForKeys: tutorials/tables/forkeys.md
- ForPairs: tutorials/tables/forpairs.md
- Animation:
- Tweens: tutorials/animation/tweens.md
- Springs: tutorials/animation/springs.md
- Roblox:
- Hydration: tutorials/roblox/hydration.md
- New Instances: tutorials/roblox/new-instances.md
- Parenting: tutorials/roblox/parenting.md
- Events: tutorials/roblox/events.md
- Change Events: tutorials/roblox/change-events.md
- Outputs: tutorials/roblox/outputs.md
- References: tutorials/roblox/references.md
- Best Practices:
- Components: tutorials/best-practices/components.md
- Instance Handling: tutorials/best-practices/instance-handling.md
- Callbacks: tutorials/best-practices/callbacks.md
- State: tutorials/best-practices/state.md
- Sharing Values: tutorials/best-practices/sharing-values.md
- Error Safety: tutorials/best-practices/error-safety.md
- Optimisation: tutorials/best-practices/optimisation.md
- Examples:
- Home: examples/index.md
- Cookbook:
- examples/cookbook/index.md
- Player List: examples/cookbook/player-list.md
- Animated Computed: examples/cookbook/animated-computed.md
- Fetch Data From Server: examples/cookbook/fetch-data-from-server.md
- Light & Dark Theme: examples/cookbook/light-and-dark-theme.md
- Button Component: examples/cookbook/button-component.md
- Loading Spinner: examples/cookbook/loading-spinner.md
- Drag & Drop: examples/cookbook/drag-and-drop.md
- API Reference:
- api-reference/index.md
- General:
- Errors: api-reference/general/errors.md
- Types:
- Contextual: api-reference/general/types/contextual.md
- Version: api-reference/general/types/version.md
- Members:
- Contextual: api-reference/general/members/contextual.md
- Safe: api-reference/general/members/safe.md
- version: api-reference/general/members/version.md
- Memory:
- Types:
- Scope: api-reference/memory/types/scope.md
- ScopedObject: api-reference/memory/types/scopedobject.md
- Task: api-reference/memory/types/task.md
- Members:
- deriveScope: api-reference/memory/members/derivescope.md
- doCleanup: api-reference/memory/members/docleanup.md
- scoped: api-reference/memory/members/scoped.md
- State:
- Types:
- UsedAs: api-reference/state/types/usedas.md
- Computed: api-reference/state/types/computed.md
- Dependency: api-reference/state/types/dependency.md
- Dependent: api-reference/state/types/dependent.md
- For: api-reference/state/types/for.md
- Observer: api-reference/state/types/observer.md
- StateObject: api-reference/state/types/stateobject.md
- Use: api-reference/state/types/use.md
- Value: api-reference/state/types/value.md
- Members:
- Computed: api-reference/state/members/computed.md
- ForKeys: api-reference/state/members/forkeys.md
- ForPairs: api-reference/state/members/forpairs.md
- ForValues: api-reference/state/members/forvalues.md
- Observer: api-reference/state/members/observer.md
- peek: api-reference/state/members/peek.md
- Value: api-reference/state/members/value.md
- Roblox:
- Types:
- Child: api-reference/roblox/types/child.md
- PropertyTable: api-reference/roblox/types/propertytable.md
- SpecialKey: api-reference/roblox/types/specialkey.md
- Members:
- Attribute: api-reference/roblox/members/attribute.md
- AttributeChange: api-reference/roblox/members/attributechange.md
- AttributeOut: api-reference/roblox/members/attributeout.md
- Children: api-reference/roblox/members/children.md
- Hydrate: api-reference/roblox/members/hydrate.md
- New: api-reference/roblox/members/new.md
- OnChange: api-reference/roblox/members/onchange.md
- OnEvent: api-reference/roblox/members/onevent.md
- Out: api-reference/roblox/members/out.md
- Ref: api-reference/roblox/members/ref.md
- Animation:
- Types:
- Animatable: api-reference/animation/types/animatable.md
- Spring: api-reference/animation/types/spring.md
- Tween: api-reference/animation/types/tween.md
- Members:
- Tween: api-reference/animation/members/tween.md
- Spring: api-reference/animation/members/spring.md
- Extras:
- Home: extras/index.md
- Backgrounds: extras/backgrounds.md
- Brand Guidelines: extras/brand-guidelines.md
markdown_extensions:
- admonition
- attr_list
- meta
- md_in_html
- pymdownx.superfences
- pymdownx.betterem
- pymdownx.details
- pymdownx.tabbed:
alternate_style: true
- pymdownx.inlinehilite
- toc:
permalink: true
- pymdownx.highlight:
guess_lang: false
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg

4464
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -2,5 +2,4 @@
wally = "upliftgames/wally@0.3.2" wally = "upliftgames/wally@0.3.2"
rojo = "rojo-rbx/rojo@7.4.4" rojo = "rojo-rbx/rojo@7.4.4"
stylua = "johnnymorganz/stylua@2.0.1" stylua = "johnnymorganz/stylua@2.0.1"
selene = "kampfkarren/selene@0.27.1"
Blink = "1Axen/Blink@0.14.1" Blink = "1Axen/Blink@0.14.1"

View file

@ -1,4 +0,0 @@
std = "roblox"
[lints]
global_usage = "allow"

View file

@ -1,25 +1,25 @@
local jecs = require("@jecs") local jecs = require("@jecs")
local pair = jecs.pair local pair = jecs.pair
local ChildOf = jecs.ChildOf local ChildOf = jecs.ChildOf
local lifetime_tracker_add = require("@tools/lifetime_tracker") local lifetime_tracker_add = require("@tools/lifetime_tracker")
local pe = require("@tools/entity_visualiser").prettify local pe = require("@tools/entity_visualiser").prettify
local world = lifetime_tracker_add(jecs.world(), {padding_enabled=false}) local world = lifetime_tracker_add(jecs.world(), {padding_enabled=false})
local FriendsWith = world:component() local FriendsWith = world:component()
world:print_snapshot() world:print_snapshot()
local e1 = world:entity() local e1 = world:entity()
local e2 = world:entity() local e2 = world:entity()
world:delete(e2) world:delete(e2)
world:print_snapshot() world:print_snapshot()
local e3 = world:entity() local e3 = world:entity()
world:add(e3, pair(ChildOf, e1)) world:add(e3, pair(ChildOf, e1))
local e4 = world:entity() local e4 = world:entity()
world:add(e4, pair(FriendsWith, e3)) world:add(e4, pair(FriendsWith, e3))
world:print_snapshot() world:print_snapshot()
world:delete(e1) world:delete(e1)
world:delete(e3) world:delete(e3)
world:print_snapshot() world:print_snapshot()
world:print_entity_index() world:print_entity_index()
world:entity() world:entity()
world:entity() world:entity()
world:print_snapshot() world:print_snapshot()

View file

@ -1,11 +0,0 @@
# Examples
This folder contains code examples for the Luau/Typescript APIs.
## Run with Luau
To run the examples with Luau, run the following commands from the root of the repository:
```sh
cd examples/luau
luau path/to/file.luau
```

View file

@ -1,43 +0,0 @@
local jecs = require("@jecs")
local world = jecs.World.new()
local Position = world:component()
local Walking = world:component()
local Name = world:component()
-- Create an entity with name Bob
local bob = world:entity()
-- The set operation finds or creates a component, and sets it.
world:set(bob, Position, Vector3.new(10, 20, 30))
-- Name the entity Bob
world:set(bob, Name, "Bob")
-- The add operation adds a component without setting a value. This is
-- useful for tags, or when adding a component with its default value.
world:add(bob, Walking)
-- Get the value for the Position component
local pos = world:get(bob, Position)
print(`\{{pos.X}, {pos.Y}, {pos.Z}\}`)
-- Overwrite the value of the Position component
world:set(bob, Position, Vector3.new(40, 50, 60))
local alice = world:entity()
-- Create another named entity
world:set(alice, Name, "Alice")
world:set(alice, Position, Vector3.new(10, 20, 30))
world:add(alice, Walking)
-- Remove tag
world:remove(alice, Walking)
-- Iterate all entities with Position
for entity, p in world:query(Position) do
print(`{entity}: \{{p.X}, {p.Y}, {p.Z}\}`)
end
-- Output:
-- {10, 20, 30}
-- Alice: {10, 20, 30}
-- Bob: {40, 50, 60}

View file

@ -1,112 +0,0 @@
local jecs = require("@jecs")
local pair = jecs.pair
local ChildOf = jecs.ChildOf
local world = jecs.World.new()
local Name = world:component()
local Position = world:component()
local Star = world:component()
local Planet = world:component()
local Moon = world:component()
local Vector3
do
Vector3 = {}
Vector3.__index = Vector3
function Vector3.new(x, y, z)
x = x or 0
y = y or 0
z = z or 0
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
end
function Vector3.__add(left, right)
return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
end
function Vector3.__mul(left, right)
if typeof(right) == "number" then
return Vector3.new(left.X * right, left.Y * right, left.Z * right)
end
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
end
Vector3.one = Vector3.new(1, 1, 1)
Vector3.zero = Vector3.new()
end
local function path(entity)
local str = world:get(entity, Name)
local parent
while true do
parent = world:parent(entity)
if not parent then
break
end
entity = parent
str = world:get(parent, Name) .. "/" .. str
end
return str
end
local function iterate(entity, parent)
local p = world:get(entity, Position)
local actual = p + parent
print(path(entity))
print(`\{{actual.X}, {actual.Y}, {actual.Z}}`)
for child in world:query(pair(ChildOf, entity)) do
--print(world:get(child, Name))
iterate(child, actual)
end
end
local sun = world:entity()
world:add(sun, Star)
world:set(sun, Position, Vector3.one)
world:set(sun, Name, "Sun")
do
local earth = world:entity()
world:set(earth, Name, "Earth")
world:add(earth, pair(ChildOf, sun))
world:add(earth, Planet)
world:set(earth, Position, Vector3.one * 3)
do
local moon = world:entity()
world:set(moon, Name, "Moon")
world:add(moon, pair(ChildOf, earth))
world:add(moon, Moon)
world:set(moon, Position, Vector3.one * 0.1)
print(`Child of Earth? {world:has(moon, pair(ChildOf, earth))}`)
end
local venus = world:entity()
world:set(venus, Name, "Venus")
world:add(venus, pair(ChildOf, sun))
world:add(venus, Planet)
world:set(venus, Position, Vector3.one * 2)
local mercury = world:entity()
world:set(mercury, Name, "Mercury")
world:add(mercury, pair(ChildOf, sun))
world:add(mercury, Planet)
world:set(mercury, Position, Vector3.one)
iterate(sun, Vector3.zero)
end
-- Output:
-- Child of Earth? true
-- Sun
-- {1, 1, 1}
-- Sun/Mercury
-- {2, 2, 2}
-- Sun/Venus
-- {3, 3, 3}
-- Sun/Earth
-- {4, 4, 4}
-- Sun/Earth/Moon
-- {4.1, 4.1, 4.1}

View file

@ -1,21 +0,0 @@
local jecs = require("@jecs")
local world = jecs.World.new()
local Model = world:component()
-- It is important to define hooks for the component before the component is ever used
-- otherwise the hooks will never invoke!
world:set(Model, jecs.OnRemove, function(entity)
-- OnRemove is invoked before the component and its value is removed
-- which provides a stable reference to the entity at deletion.
-- This means that it is safe to retrieve the data inside of a hook
local model = world:get(entity, Model)
model:Destroy()
end)
world:set(Model, jecs.OnSet, function(entity, model)
-- OnSet is invoked after the data has been assigned.
-- It also returns the data for faster access.
-- There may be some logic to do some side effects on reassignments
model:SetAttribute("entityId", entity)
end)

View file

@ -1,63 +0,0 @@
local jecs = require("@jecs")
local world = jecs.World.new()
local Position = world:component()
local Velocity = world:component()
local Name = world:component()
local Vector3
do
Vector3 = {}
Vector3.__index = Vector3
function Vector3.new(x, y, z)
x = x or 0
y = y or 0
z = z or 0
return setmetatable({ X = x, Y = y, Z = z }, Vector3)
end
function Vector3.__add(left, right)
return Vector3.new(left.X + right.X, left.Y + right.Y, left.Z + right.Z)
end
function Vector3.__mul(left, right)
if typeof(right) == "number" then
return Vector3.new(left.X * right, left.Y * right, left.Z * right)
end
return Vector3.new(left.X * right.X, left.Y * right.Y, left.Z * right.Z)
end
Vector3.one = Vector3.new(1, 1, 1)
Vector3.zero = Vector3.new()
end
-- Create a few test entities for a Position, Velocity query
local e1 = world:entity()
world:set(e1, Name, "e1")
world:set(e1, Position, Vector3.new(10, 20, 30))
world:set(e1, Velocity, Vector3.new(1, 2, 3))
local e2 = world:entity()
world:set(e2, Name, "e2")
world:set(e2, Position, Vector3.new(10, 20, 30))
world:set(e2, Velocity, Vector3.new(4, 5, 6))
-- This entity will not match as it does not have Position, Velocity
local e3 = world:entity()
world:set(e3, Name, "e3")
world:set(e3, Position, Vector3.new(10, 20, 30))
-- Create an uncached query for Position, Velocity.
for entity, p, v in world:query(Position, Velocity) do
-- Iterate entities matching the query
p.X += v.X
p.Y += v.Y
p.Z += v.Z
print(`{world:get(entity, Name)}: \{{p.X}, {p.Y}, {p.Z}}`)
end
-- Output:
-- e2: {14, 25, 36}
-- e1: {11, 22, 33}

View file

@ -1,61 +0,0 @@
local jecs = require("@jecs")
local world = jecs.World.new()
local Name = world:component()
local function named(ctr, name)
local e = ctr(world)
world:set(e, Name, name)
return e
end
local function name(e)
return world:get(e, Name)
end
local Position = named(world.component, "Position") :: jecs.Entity<Vector3>
local Previous = jecs.Rest
local PreviousPosition = jecs.pair(Previous, Position)
local added = world
:query(Position)
:without(PreviousPosition)
:cached()
local changed = world
:query(Position, PreviousPosition)
:cached()
local removed = world
:query(PreviousPosition)
:without(Position)
:cached()
local e1 = named(world.entity, "e1")
world:set(e1, Position, Vector3.new(10, 20, 30))
local e2 = named(world.entity, "e2")
world:set(e2, Position, Vector3.new(10, 20, 30))
for e, p in added:iter() do
print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`)
world:set(e, PreviousPosition, p)
end
world:set(e1, Position, "")
for e, new, old in changed:iter() do
if new ~= old then
print(`{name(new)}'s Position changed from \{{old.X}, {old.Y}, {old.Z}\} to \{{new.X}, {new.Y}, {new.Z}\}`)
world:set(e, PreviousPosition, new)
end
end
world:remove(e2, Position)
for e in removed:iter() do
print(`Position was removed from {name(e)}`)
end
-- Output:
-- Added 265: {10, 20, 30}
-- Added 264: {10, 20, 30}
-- e1's Position changed from {10, 20, 30} to {999, 999, 1998}
-- Position was removed from e2

View file

@ -1,125 +0,0 @@
local jecs = require("@jecs")
local pair = jecs.pair
local ChildOf = jecs.ChildOf
local __ = jecs.Wildcard
local Name = jecs.Name
local world = jecs.World.new()
type Id<T = nil> = number & { __T: T }
local Voxel = world:component() :: Id
local Position = world:component() :: Id<Vector3>
local Perception = world:component() :: Id<{
range: number,
fov: number,
dir: Vector3,
}>
local PrimaryPart = world:component() :: Id<Part>
local local_player = game:GetService("Players").LocalPlayer
local function distance(a: Vector3, b: Vector3)
return (b - a).Magnitude
end
local function is_in_fov(a: Vector3, b: Vector3, forward_dir: Vector3, fov_angle: number)
local to_target = b - a
local forward_xz = Vector3.new(forward_dir.X, 0, forward_dir.Z).Unit
local to_target_xz = Vector3.new(to_target.X, 0, to_target.Z).Unit
local angle_to_target = math.deg(math.atan2(to_target_xz.Z, to_target_xz.X))
local forward_angle = math.deg(math.atan2(forward_xz.Z, forward_xz.X))
local angle_difference = math.abs(forward_angle - angle_to_target)
if angle_difference > 180 then
angle_difference = 360 - angle_difference
end
return angle_difference <= (fov_angle / 2)
end
local map = {}
local grid = 50
local function add_to_voxel(source: number, position: Vector3, prev_voxel_id: number?)
local hash = position // grid
local voxel_id = map[hash]
if not voxel_id then
voxel_id = world:entity()
world:add(voxel_id, Voxel)
world:set(voxel_id, Position, hash)
map[hash] = voxel_id
end
if prev_voxel_id ~= nil then
world:remove(source, pair(ChildOf, prev_voxel_id))
end
world:add(source, pair(ChildOf, voxel_id))
end
local function reconcile_client_owned_assembly_to_voxel(dt: number)
for e, part, position in world:query(PrimaryPart, Position) do
local p = part.Position
if p ~= position then
world:set(e, Position, p)
local voxel_id = world:target(e, ChildOf, 0)
if map[p // grid] == voxel_id then
continue
end
add_to_voxel(e, p, voxel_id)
end
end
end
local function update_camera_direction(dt: number)
for _, perception in world:query(Perception) do
perception.dir = workspace.CurrentCamera.CFrame.LookVector
end
end
local function perceive_enemies(dt: number)
local it = world:query(Perception, Position, PrimaryPart)
-- There is only going to be one entity matching the query
local e, self_perception, self_position, self_primary_part = it()
local voxel_id = map[self_primary_part.Position // grid]
local nearby_entities_query = world:query(Position, pair(ChildOf, voxel_id))
for enemy, target_position in nearby_entities_query do
if distance(self_position, target_position) > self_perception.range then
continue
end
if is_in_fov(self_position, target_position, self_perception.dir, self_perception.fov) then
local p = target_position
print(`Entity {world:get(e, Name)} can see target {world:get(enemy, Name)} at ({p.X}, {p.Y}, {p.Z})`)
end
end
end
local player = world:entity()
world:set(player, Perception, {
range = 100,
fov = 90,
dir = Vector3.new(1, 0, 0),
})
world:set(player, Name, "LocalPlayer")
local primary_part = (local_player.Character :: Model).PrimaryPart :: Part
world:set(player, PrimaryPart, primary_part)
world:set(player, Position, Vector3.zero)
local enemy = world:entity()
world:set(enemy, Name, "Enemy $1")
world:set(enemy, Position, Vector3.new(50, 0, 20))
add_to_voxel(player, primary_part.Position)
add_to_voxel(enemy, world)
local dt = 1 / 60
reconcile_client_owned_assembly_to_voxel(dt)
update_camera_direction(dt)
perceive_enemies(dt)
-- Output:
-- LocalPlayer can see target Enemy $1

View file

@ -1,37 +0,0 @@
local jecs = require("@jecs")
local pair = jecs.pair
local world = jecs.World.new()
local Name = world:component()
local function named(ctr, name)
local e = ctr(world)
world:set(e, Name, name)
return e
end
local function name(e)
return world:get(e, Name)
end
local Eats = world:component()
local Apples = named(world.entity, "Apples")
local Oranges = named(world.entity, "Oranges")
local bob = named(world.entity, "Bob")
world:set(bob, pair(Eats, Apples), 10)
local alice = named(world.entity, "Alice")
world:set(alice, pair(Eats, Oranges), 5)
-- Aliasing the wildcard to symbols improves readability and ease of writing
local __ = jecs.Wildcard
-- Create a query that matches edible components
for entity, amount in world:query(pair(Eats, __)) do
-- Iterate the query
local food = world:target(entity, Eats)
print(`{name(entity)} eats {amount} {name(food)}`)
end
-- Output:
-- Alice eats 5 Oranges
-- Bob eats 10 Apples

View file

@ -1,158 +1,158 @@
local c = { local c = {
white_underline = function(s: any) white_underline = function(s: any)
return `\27[1;4m{s}\27[0m` return `\27[1;4m{s}\27[0m`
end, end,
white = function(s: any) white = function(s: any)
return `\27[37;1m{s}\27[0m` return `\27[37;1m{s}\27[0m`
end, end,
green = function(s: any) green = function(s: any)
return `\27[32;1m{s}\27[0m` return `\27[32;1m{s}\27[0m`
end, end,
red = function(s: any) red = function(s: any)
return `\27[31;1m{s}\27[0m` return `\27[31;1m{s}\27[0m`
end, end,
yellow = function(s: any) yellow = function(s: any)
return `\27[33;1m{s}\27[0m` return `\27[33;1m{s}\27[0m`
end, end,
red_highlight = function(s: any) red_highlight = function(s: any)
return `\27[41;1;30m{s}\27[0m` return `\27[41;1;30m{s}\27[0m`
end, end,
green_highlight = function(s: any) green_highlight = function(s: any)
return `\27[42;1;30m{s}\27[0m` return `\27[42;1;30m{s}\27[0m`
end, end,
gray = function(s: any) gray = function(s: any)
return `\27[30;1m{s}\27[0m` return `\27[30;1m{s}\27[0m`
end, end,
} }
local ECS_PAIR_FLAG = 0x8 local ECS_PAIR_FLAG = 0x8
local ECS_ID_FLAGS_MASK = 0x10 local ECS_ID_FLAGS_MASK = 0x10
local ECS_ENTITY_MASK = bit32.lshift(1, 24) local ECS_ENTITY_MASK = bit32.lshift(1, 24)
local ECS_GENERATION_MASK = bit32.lshift(1, 16) local ECS_GENERATION_MASK = bit32.lshift(1, 16)
type i53 = number type i53 = number
type i24 = number type i24 = number
local function ECS_ENTITY_T_LO(e: i53): i24 local function ECS_ENTITY_T_LO(e: i53): i24
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) // ECS_ENTITY_MASK else e
end end
local function ECS_GENERATION(e: i53): i24 local function ECS_GENERATION(e: i53): i24
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_GENERATION_MASK else 0 return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_GENERATION_MASK else 0
end end
local ECS_ID = ECS_ENTITY_T_LO local ECS_ID = ECS_ENTITY_T_LO
local function ECS_COMBINE(source: number, target: number): i53 local function ECS_COMBINE(source: number, target: number): i53
return (source * 268435456) + (target * ECS_ID_FLAGS_MASK) return (source * 268435456) + (target * ECS_ID_FLAGS_MASK)
end end
local function ECS_GENERATION_INC(e: i53) local function ECS_GENERATION_INC(e: i53)
if e > ECS_ENTITY_MASK then if e > ECS_ENTITY_MASK then
local flags = e // ECS_ID_FLAGS_MASK local flags = e // ECS_ID_FLAGS_MASK
local id = flags // ECS_ENTITY_MASK local id = flags // ECS_ENTITY_MASK
local generation = flags % ECS_GENERATION_MASK local generation = flags % ECS_GENERATION_MASK
local next_gen = generation + 1 local next_gen = generation + 1
if next_gen > ECS_GENERATION_MASK then if next_gen > ECS_GENERATION_MASK then
return id return id
end end
return ECS_COMBINE(id, next_gen) + flags return ECS_COMBINE(id, next_gen) + flags
end end
return ECS_COMBINE(e, 1) return ECS_COMBINE(e, 1)
end end
local function bl() local function bl()
print("") print("")
end end
local function pe(e) local function pe(e)
local gen = ECS_GENERATION(e) local gen = ECS_GENERATION(e)
return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`) return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`)
end end
local function dprint(tbl: { [number]: number }) local function dprint(tbl: { [number]: number })
bl() bl()
print("--------") print("--------")
for i, e in tbl do for i, e in tbl do
print("| "..pe(e).." |") print("| "..pe(e).." |")
print("--------") print("--------")
end end
bl() bl()
end end
local max_id = 0 local max_id = 0
local alive_count = 0 local alive_count = 0
local dense = {} local dense = {}
local sparse = {} local sparse = {}
local function alloc() local function alloc()
if alive_count ~= #dense then if alive_count ~= #dense then
alive_count += 1 alive_count += 1
print("*recycled", pe(dense[alive_count])) print("*recycled", pe(dense[alive_count]))
return dense[alive_count] return dense[alive_count]
end end
max_id += 1 max_id += 1
local id = max_id local id = max_id
alive_count += 1 alive_count += 1
dense[alive_count] = id dense[alive_count] = id
sparse[id] = { sparse[id] = {
dense = alive_count dense = alive_count
} }
print("*allocated", pe(id)) print("*allocated", pe(id))
return id return id
end end
local function remove(entity) local function remove(entity)
local id = ECS_ID(entity) local id = ECS_ID(entity)
local r = sparse[id] local r = sparse[id]
local index_of_deleted_entity = r.dense local index_of_deleted_entity = r.dense
local last_entity_alive_at_index = alive_count -- last entity alive local last_entity_alive_at_index = alive_count -- last entity alive
alive_count -= 1 alive_count -= 1
local last_alive_entity = dense[last_entity_alive_at_index] local last_alive_entity = dense[last_entity_alive_at_index]
local r_swap = sparse[ECS_ID(last_alive_entity)] local r_swap = sparse[ECS_ID(last_alive_entity)]
r_swap.dense = r.dense r_swap.dense = r.dense
r.dense = last_entity_alive_at_index r.dense = last_entity_alive_at_index
dense[index_of_deleted_entity] = last_alive_entity dense[index_of_deleted_entity] = last_alive_entity
dense[last_entity_alive_at_index] = ECS_GENERATION_INC(entity) dense[last_entity_alive_at_index] = ECS_GENERATION_INC(entity)
print("*dellocated", pe(id)) print("*dellocated", pe(id))
end end
local function alive(e) local function alive(e)
local r = sparse[ECS_ID(e)] local r = sparse[ECS_ID(e)]
return dense[r.dense] == e return dense[r.dense] == e
end end
local function pa(e) local function pa(e)
print(`{pe(e)} is {if alive(e) then "alive" else "not alive"}`) print(`{pe(e)} is {if alive(e) then "alive" else "not alive"}`)
end end
local tprint = require("@testkit").print local tprint = require("@testkit").print
local e1v0 = alloc() local e1v0 = alloc()
local e2v0 = alloc() local e2v0 = alloc()
local e3v0 = alloc() local e3v0 = alloc()
local e4v0 = alloc() local e4v0 = alloc()
local e5v0 = alloc() local e5v0 = alloc()
pa(e1v0) pa(e1v0)
pa(e4v0) pa(e4v0)
remove(e5v0) remove(e5v0)
pa(e5v0) pa(e5v0)
local e5v1 = alloc() local e5v1 = alloc()
pa(e5v0) pa(e5v0)
pa(e5v1) pa(e5v1)
pa(e2v0) pa(e2v0)
print(ECS_ID(e2v0)) print(ECS_ID(e2v0))
dprint(dense) dprint(dense)
remove(e2v0) remove(e2v0)
dprint(dense) dprint(dense)

View file

@ -1,122 +1,122 @@
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
_G.__JECS_HI_COMPONENT_ID = 300 _G.__JECS_HI_COMPONENT_ID = 300
local ecs = require(ReplicatedStorage.ecs) local ecs = require(ReplicatedStorage.ecs)
-- 500 entities -- 500 entities
-- 2-30 components on each entity -- 2-30 components on each entity
-- 300 unique components -- 300 unique components
-- 200 systems -- 200 systems
-- 1-10 components to query per system -- 1-10 components to query per system
local startTime = os.clock() local startTime = os.clock()
local world = ecs.World.new() local world = ecs.World.new()
local components = {} local components = {}
for i = 1, 300 do -- 300 components for i = 1, 300 do -- 300 components
components[i] = world:component() components[i] = world:component()
end end
local archetypes = {} local archetypes = {}
for i = 1, 50 do -- 50 archetypes for i = 1, 50 do -- 50 archetypes
local archetype = {} local archetype = {}
for _ = 1, math.random(2, 30) do for _ = 1, math.random(2, 30) do
local componentId = math.random(1, #components) local componentId = math.random(1, #components)
table.insert(archetype, components[componentId]) table.insert(archetype, components[componentId])
end end
archetypes[i] = archetype archetypes[i] = archetype
end end
for _ = 1, 1000 do -- 1000 entities in the world for _ = 1, 1000 do -- 1000 entities in the world
local componentsToAdd = {} local componentsToAdd = {}
local archetypeId = math.random(1, #archetypes) local archetypeId = math.random(1, #archetypes)
local e = world:entity() local e = world:entity()
for _, component in ipairs(archetypes[archetypeId]) do for _, component in ipairs(archetypes[archetypeId]) do
world:set(e, component, { world:set(e, component, {
DummyData = math.random(1, 5000), DummyData = math.random(1, 5000),
}) })
end end
end end
local function values(t) local function values(t)
local array = {} local array = {}
for _, v in t do for _, v in t do
table.insert(array, v) table.insert(array, v)
end end
return array return array
end end
local contiguousComponents = values(components) local contiguousComponents = values(components)
local systemComponentsToQuery = {} local systemComponentsToQuery = {}
for _ = 1, 200 do -- 200 systems for _ = 1, 200 do -- 200 systems
local numComponentsToQuery = math.random(1, 10) local numComponentsToQuery = math.random(1, 10)
local componentsToQuery = {} local componentsToQuery = {}
for _ = 1, numComponentsToQuery do for _ = 1, numComponentsToQuery do
table.insert(componentsToQuery, contiguousComponents[math.random(1, #contiguousComponents)]) table.insert(componentsToQuery, contiguousComponents[math.random(1, #contiguousComponents)])
end end
table.insert(systemComponentsToQuery, componentsToQuery) table.insert(systemComponentsToQuery, componentsToQuery)
end end
local worldCreateTime = os.clock() - startTime local worldCreateTime = os.clock() - startTime
local results = {} local results = {}
startTime = os.clock() startTime = os.clock()
RunService.Heartbeat:Connect(function() RunService.Heartbeat:Connect(function()
local added = 0 local added = 0
local systemStartTime = os.clock() local systemStartTime = os.clock()
debug.profilebegin("systems") debug.profilebegin("systems")
for _, componentsToQuery in ipairs(systemComponentsToQuery) do for _, componentsToQuery in ipairs(systemComponentsToQuery) do
debug.profilebegin("system") debug.profilebegin("system")
for entityId, firstComponent in world:query(unpack(componentsToQuery)) do for entityId, firstComponent in world:query(unpack(componentsToQuery)) do
world:set( world:set(
entityId, entityId,
{ {
DummyData = firstComponent.DummyData + 1, DummyData = firstComponent.DummyData + 1,
} }
) )
added += 1 added += 1
end end
debug.profileend() debug.profileend()
end end
debug.profileend() debug.profileend()
if os.clock() - startTime < 4 then if os.clock() - startTime < 4 then
-- discard first 4 seconds -- discard first 4 seconds
return return
end end
if results == nil then if results == nil then
return return
elseif #results < 1000 then elseif #results < 1000 then
table.insert(results, os.clock() - systemStartTime) table.insert(results, os.clock() - systemStartTime)
else else
print("added", added) print("added", added)
print("World created in", worldCreateTime * 1000, "ms") print("World created in", worldCreateTime * 1000, "ms")
local sum = 0 local sum = 0
for _, result in ipairs(results) do for _, result in ipairs(results) do
sum += result sum += result
end end
print(("Average frame time: %fms"):format((sum / #results) * 1000)) print(("Average frame time: %fms"):format((sum / #results) * 1000))
results = nil results = nil
local n = #world.archetypes local n = #world.archetypes
print( print(
("X entities\n%d components\n%d systems\n%d archetypes"):format( ("X entities\n%d components\n%d systems\n%d archetypes"):format(
#components, #components,
#systemComponentsToQuery, #systemComponentsToQuery,
n n
) )
) )
end end
end) end)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

View file

@ -1,24 +0,0 @@
declare function afterAll(callback: () -> ()): ()
declare function afterEach(callback: () -> ()): ()
declare function beforeAll(callback: () -> ()): ()
declare function beforeEach(callback: () -> ()): ()
declare function describe(phrase: string, callback: () -> ()): ()
declare function describeFOCUS(phrase: string, callback: () -> ()): ()
declare function fdescribe(phrase: string, callback: () -> ()): ()
declare function describeSKIP(phrase: string, callback: () -> ()): ()
declare function xdescribe(phrase: string, callback: () -> ()): ()
declare function expect(value: any): any
declare function FIXME(optionalMessage: string?): ()
declare function FOCUS(): ()
declare function SKIP(): ()
declare function it(phrase: string, callback: () -> ()): ()
declare function itFOCUS(phrase: string, callback: () -> ()): ()
declare function fit(phrase: string, callback: () -> ()): ()
declare function itSKIP(phrase: string, callback: () -> ()): ()
declare function xit(phrase: string, callback: () -> ()): ()
declare function itFIXME(phrase: string, callback: () -> ()): ()

View file

@ -1,33 +1,33 @@
return { return {
white_underline = function(s: any) white_underline = function(s: any)
return `\27[1;4m{s}\27[0m` return `\27[1;4m{s}\27[0m`
end, end,
white = function(s: any) white = function(s: any)
return `\27[37;1m{s}\27[0m` return `\27[37;1m{s}\27[0m`
end, end,
green = function(s: any) green = function(s: any)
return `\27[32;1m{s}\27[0m` return `\27[32;1m{s}\27[0m`
end, end,
red = function(s: any) red = function(s: any)
return `\27[31;1m{s}\27[0m` return `\27[31;1m{s}\27[0m`
end, end,
yellow = function(s: any) yellow = function(s: any)
return `\27[33;1m{s}\27[0m` return `\27[33;1m{s}\27[0m`
end, end,
red_highlight = function(s: any) red_highlight = function(s: any)
return `\27[41;1;30m{s}\27[0m` return `\27[41;1;30m{s}\27[0m`
end, end,
green_highlight = function(s: any) green_highlight = function(s: any)
return `\27[42;1;30m{s}\27[0m` return `\27[42;1;30m{s}\27[0m`
end, end,
gray = function(s: any) gray = function(s: any)
return `\27[30;1m{s}\27[0m` return `\27[30;1m{s}\27[0m`
end, end,
} }

View file

@ -1,43 +1,43 @@
local jecs = require("@jecs") local jecs = require("@jecs")
local ECS_GENERATION = jecs.ECS_GENERATION local ECS_GENERATION = jecs.ECS_GENERATION
local ECS_ID = jecs.ECS_ID local ECS_ID = jecs.ECS_ID
local ansi = require("@tools/ansi") local ansi = require("@tools/ansi")
local function pe(e: any) local function pe(e: any)
local gen = ECS_GENERATION(e) local gen = ECS_GENERATION(e)
return ansi.green(`e{ECS_ID(e)}`)..ansi.yellow(`v{gen}`) return ansi.green(`e{ECS_ID(e)}`) .. ansi.yellow(`v{gen}`)
end end
local function name(world: jecs.World, id: any) local function name(world: jecs.World, id: any)
return world:get(id, jecs.Name) or `${id}` return world:get(id, jecs.Name) or `${id}`
end end
local function components(world: jecs.World, entity: any) local function components(world: jecs.World, entity: any)
local r = jecs.entity_index_try_get(world.entity_index, entity) local r = jecs.entity_index_try_get(world.entity_index, entity)
if not r then if not r then
return false return false
end end
local archetype = r.archetype local archetype = r.archetype
local row = r.row local row = r.row
print(`Entity {pe(entity)}`) print(`Entity {pe(entity)}`)
print("-----------------------------------------------------") print("-----------------------------------------------------")
for i, column in archetype.columns do for i, column in archetype.columns do
local component = archetype.types[i] local component = archetype.types[i]
local n local n
if jecs.IS_PAIR(component) then if jecs.IS_PAIR(component) then
n = `({name(world, jecs.pair_first(world, component))}, {name(world, jecs.pair_second(world, component))})` n = `({name(world, jecs.pair_first(world, component))}, {name(world, jecs.pair_second(world, component))})`
else else
n = name(world, component) n = name(world, component)
end end
local data = column[row] or "TAG" local data = column[row] or "TAG"
print(`| {n} | {data} |`) print(`| {n} | {data} |`)
end end
print("-----------------------------------------------------") print("-----------------------------------------------------")
return true return true
end end
return { return {
components = components, components = components,
prettify = pe, prettify = pe,
} }

View file

@ -1,215 +1,216 @@
local jecs = require("@jecs") local jecs = require("@jecs")
local ECS_GENERATION = jecs.ECS_GENERATION local ECS_GENERATION = jecs.ECS_GENERATION
local ECS_ID = jecs.ECS_ID local ECS_ID = jecs.ECS_ID
local __ = jecs.Wildcard local __ = jecs.Wildcard
local pair = jecs.pair local pair = jecs.pair
local prettify = require("@tools/entity_visualiser").prettify local prettify = require("@tools/entity_visualiser").prettify
local pe = prettify local pe = prettify
local ansi = require("@tools/ansi") local ansi = require("@tools/ansi")
function print_centered_entity(entity, width: number) function print_centered_entity(entity, width: number)
local entity_str = tostring(entity) local entity_str = tostring(entity)
local entity_length = #entity_str local entity_length = #entity_str
local padding_total = width - 2 - entity_length local padding_total = width - 2 - entity_length
local padding_left = math.floor(padding_total / 2) local padding_left = math.floor(padding_total / 2)
local padding_right = padding_total - padding_left local padding_right = padding_total - padding_left
local centered_str = string.rep(" ", padding_left) .. entity_str .. string.rep(" ", padding_right) local centered_str = string.rep(" ", padding_left) .. entity_str .. string.rep(" ", padding_right)
print("|" .. centered_str .. "|") print("|" .. centered_str .. "|")
end end
local function name(world, e) local function name(world, e)
return world:get(world, e, jecs.Name) or pe(e) return world:get(world, e, jecs.Name) or pe(e)
end end
local padding_enabled = false local padding_enabled = false
local function pad() local function pad()
if padding_enabled then if padding_enabled then
print("") print("")
end end
end end
local function lifetime_tracker_add(world: jecs.World, opt) local function lifetime_tracker_add(world: jecs.World, opt)
local entity_index = world.entity_index local entity_index = world.entity_index
local dense_array = entity_index.dense_array local dense_array = entity_index.dense_array
local component_index = world.component_index local component_index = world.component_index
local ENTITY_RANGE = (jecs.Rest :: any) + 1 local ENTITY_RANGE = (jecs.Rest :: any) + 1
local w = setmetatable({}, { __index = world }) local w = setmetatable({}, { __index = world })
padding_enabled = opt.padding_enabled padding_enabled = opt.padding_enabled
local world_entity = world.entity local world_entity = world.entity
w.entity = function(self, entity) w.entity = function(self, entity)
if entity then if entity then
return world_entity(world, entity) return world_entity(world, entity)
end end
local will_recycle = entity_index.max_id ~= entity_index.alive_count local will_recycle = entity_index.max_id ~= entity_index.alive_count
local e = world_entity(world) local e = world_entity(world)
if will_recycle then if will_recycle then
print(`*recycled {pe(e)}`) print(`*recycled {pe(e)}`)
else else
print(`*created {pe(e)}`) print(`*created {pe(e)}`)
end end
pad() pad()
return e return e
end end
w.print_entity_index = function(self) w.print_entity_index = function(self)
local max_id = entity_index.max_id local max_id = entity_index.max_id
local alive_count = entity_index.alive_count local alive_count = entity_index.alive_count
local alive = table.move(dense_array, 1+jecs.Rest::any, alive_count, 1, {}) local alive = table.move(dense_array, 1 + jecs.Rest :: any, alive_count, 1, {})
local dead = table.move(dense_array, alive_count + 1, max_id, 1, {}) local dead = table.move(dense_array, alive_count + 1, max_id, 1, {})
local sep = "|--------|" local sep = "|--------|"
if #alive > 0 then if #alive > 0 then
print("|-alive--|") print("|-alive--|")
for i = 1, #alive do for i = 1, #alive do
local e = pe(alive[i]) local e = pe(alive[i])
print_centered_entity(e, 32) print_centered_entity(e, 32)
print(sep) print(sep)
end end
print("\n") print("\n")
end end
if #dead > 0 then if #dead > 0 then
print("|--dead--|") print("|--dead--|")
for i = 1, #dead do for i = 1, #dead do
print_centered_entity(pe(dead[i]), 32) print_centered_entity(pe(dead[i]), 32)
print(sep) print(sep)
end end
end end
pad() pad()
end end
local timelines = {} local timelines = {}
w.print_snapshot = function(self) w.print_snapshot = function(self)
local timeline = #timelines + 1 local timeline = #timelines + 1
local entity_column_width = 10 local entity_column_width = 10
local status_column_width = 8 local status_column_width = 8
local header = string.format("| %-" .. entity_column_width .. "s |", "Entity") local header = string.format("| %-" .. entity_column_width .. "s |", "Entity")
for i = 1, timeline do for i = 1, timeline do
header = header .. string.format(" %-" .. status_column_width .. "s |", string.format("T%d", i)) header = header .. string.format(" %-" .. status_column_width .. "s |", string.format("T%d", i))
end end
local max_id = entity_index.max_id local max_id = entity_index.max_id
local alive_count = entity_index.alive_count local alive_count = entity_index.alive_count
local alive = table.move(dense_array, 1+jecs.Rest::any, alive_count, 1, {}) local alive = table.move(dense_array, 1 + jecs.Rest :: any, alive_count, 1, {})
local dead = table.move(dense_array, alive_count + 1, max_id, 1, {}) local dead = table.move(dense_array, alive_count + 1, max_id, 1, {})
local data = {} local data = {}
print("-------------------------------------------------------------------") print("-------------------------------------------------------------------")
print(header) print(header)
-- Store the snapshot data for this timeline -- Store the snapshot data for this timeline
for i = ENTITY_RANGE, max_id do for i = ENTITY_RANGE, max_id do
if dense_array[i] then if dense_array[i] then
local entity = dense_array[i] local entity = dense_array[i]
local id = ECS_ID(entity) local id = ECS_ID(entity)
local status = "alive" local status = "alive"
if not world:contains(entity) then if not world:contains(entity) then
status = "dead" status = "dead"
end end
data[id] = status data[id] = status
end end
end end
table.insert(timelines, data) table.insert(timelines, data)
-- Create a table to hold entity data for sorting -- Create a table to hold entity data for sorting
local entities = {} local entities = {}
for i = ENTITY_RANGE, max_id do for i = ENTITY_RANGE, max_id do
if dense_array[i] then if dense_array[i] then
local entity = dense_array[i] local entity = dense_array[i]
local id = ECS_ID(entity) local id = ECS_ID(entity)
-- Push entity and id into the new `entities` table -- Push entity and id into the new `entities` table
table.insert(entities, {entity = entity, id = id}) table.insert(entities, { entity = entity, id = id })
end end
end end
-- Sort the entities by ECS_ID -- Sort the entities by ECS_ID
table.sort(entities, function(a, b) table.sort(entities, function(a, b)
return a.id < b.id return a.id < b.id
end) end)
-- Print the sorted rows -- Print the sorted rows
for _, entity_data in ipairs(entities) do for _, entity_data in ipairs(entities) do
local entity = entity_data.entity local entity = entity_data.entity
local id = entity_data.id local id = entity_data.id
local status = "alive" local status = "alive"
if id > alive_count then if id > alive_count then
status = "dead" status = "dead"
end end
local row = string.format("| %-" .. entity_column_width .. "s |", pe(entity)) local row = string.format("| %-" .. entity_column_width .. "s |", pe(entity))
for j = 1, timeline do for j = 1, timeline do
local timeline_data = timelines[j] local timeline_data = timelines[j]
local entity_data = timeline_data[id] local entity_data = timeline_data[id]
if entity_data then if entity_data then
row = row .. string.format(" %-" .. status_column_width .. "s |", entity_data) row = row .. string.format(" %-" .. status_column_width .. "s |", entity_data)
else else
row = row .. string.format(" %-" .. status_column_width .. "s |", "-") row = row .. string.format(" %-" .. status_column_width .. "s |", "-")
end end
end end
print(row) print(row)
end end
print("-------------------------------------------------------------------") print("-------------------------------------------------------------------")
pad() pad()
end end
local world_add = world.add local world_add = world.add
local relations = {} local relations = {}
w.add = function(self, entity: any, component: any) w.add = function(self, entity: any, component: any)
world_add(world, entity, component) world_add(world, entity, component)
if jecs.IS_PAIR(component) then if jecs.IS_PAIR(component) then
local relation = jecs.pair_first(world, component) local relation = jecs.pair_first(world, component)
local target = jecs.pair_second(world, component) local target = jecs.pair_second(world, component)
print(`*added ({pe(relation)}, {pe(target)}) to {pe(entity)}`) print(`*added ({pe(relation)}, {pe(target)}) to {pe(entity)}`)
pad() pad()
end end
end
end
local world_delete = world.delete
local world_delete = world.delete w.delete = function(self, e)
w.delete = function(self, e) world_delete(world, e)
world_delete(world, e)
local idr_t = component_index[pair(__, e)]
local idr_t = component_index[pair(__, e)] if idr_t then
if idr_t then for archetype_id in idr_t.cache do
for archetype_id in idr_t.cache do local archetype = world.archetypes[archetype_id]
local archetype = world.archetypes[archetype_id] for _, id in archetype.types do
for _, id in archetype.types do if not jecs.IS_PAIR(id) then
if not jecs.IS_PAIR(id) then continue
continue end
end local object = jecs.pair_second(world, id)
local object = jecs.pair_second(world, id) if object ~= e then
if object ~= e then continue
continue end
end local id_record = component_index[id]
local id_record = component_index[id] local flags = id_record.flags
local flags = id_record.flags local flags_delete_mask: number = bit32.band(flags, jecs.ECS_ID_DELETE)
local flags_delete_mask: number = bit32.band(flags, jecs.ECS_ID_DELETE) if flags_delete_mask ~= 0 then
if flags_delete_mask ~= 0 then for _, entity in archetype.entities do
for _, entity in archetype.entities do print(`*deleted dependant {pe(entity)} of {pe(e)}`)
print(`*deleted dependant {pe(entity)} of {pe(e)}`) pad()
pad() end
end break
break else
else for _, entity in archetype.entities do
for _, entity in archetype.entities do print(
print(`*removed dependency ({pe(jecs.pair_first(world, id))}, {pe(object)}) from {pe(entity)}`) `*removed dependency ({pe(jecs.pair_first(world, id))}, {pe(object)}) from {pe(entity)}`
end )
end end
end end
end end
end end
end
print(`*deleted {pe(e)}`)
pad() print(`*deleted {pe(e)}`)
end pad()
return w end
end return w
end
return lifetime_tracker_add
return lifetime_tracker_add

View file

@ -1,108 +1,108 @@
local function dbg_info(n: number): any local function dbg_info(n: number): any
return debug.info(n, "s") return debug.info(n, "s")
end end
local function throw(msg: string) local function throw(msg: string)
local s = 1 local s = 1
local root = dbg_info(1) local root = dbg_info(1)
repeat repeat
s += 1 s += 1
until dbg_info(s) ~= root until dbg_info(s) ~= root
if warn then if warn then
error(msg, s) error(msg, s)
else else
print(`[jecs] error: {msg}\n`) print(`[jecs] error: {msg}\n`)
end end
end end
local function ASSERT<T>(v: T, msg: string) local function ASSERT<T>(v: T, msg: string)
if v then if v then
return return
end end
throw(msg) throw(msg)
end end
local function runtime_lints_add(world) local function runtime_lints_add(world)
local function get_name(id) local function get_name(id)
return world_get_one_inline(world, id, EcsName) return world_get_one_inline(world, id, EcsName)
end end
local function bname(id): string local function bname(id): string
local name: string local name: string
if ECS_IS_PAIR(id) then if ECS_IS_PAIR(id) then
local first = get_name(world, ecs_pair_first(world, id)) local first = get_name(world, ecs_pair_first(world, id))
local second = get_name(world, ecs_pair_second(world, id)) local second = get_name(world, ecs_pair_second(world, id))
name = `pair({first}, {second})` name = `pair({first}, {second})`
else else
return get_name(world, id) return get_name(world, id)
end end
if name then if name then
return name return name
else else
return `${id}` return `${id}`
end end
end end
local function ID_IS_TAG(world: World, id) local function ID_IS_TAG(world: World, id)
if ECS_IS_PAIR(id) then if ECS_IS_PAIR(id) then
id = ecs_pair_first(world, id) id = ecs_pair_first(world, id)
end end
return not world_has_one_inline(world, id, EcsComponent) return not world_has_one_inline(world, id, EcsComponent)
end end
World.query = function(world: World, ...) World.query = function(world: World, ...)
ASSERT((...), "Requires at least a single component") ASSERT((...), "Requires at least a single component")
return world_query(world, ...) return world_query(world, ...)
end end
World.set = function(world: World, entity: i53, id: i53, value: any): () World.set = function(world: World, entity: i53, id: i53, value: any): ()
local is_tag = ID_IS_TAG(world, id) local is_tag = ID_IS_TAG(world, id)
if is_tag and value == nil then if is_tag and value == nil then
local _1 = bname(world, entity) local _1 = bname(world, entity)
local _2 = bname(world, id) local _2 = bname(world, id)
local why = "cannot set component value to nil" local why = "cannot set component value to nil"
throw(why) throw(why)
return return
elseif value ~= nil and is_tag then elseif value ~= nil and is_tag then
local _1 = bname(world, entity) local _1 = bname(world, entity)
local _2 = bname(world, id) local _2 = bname(world, id)
local why = `cannot set a component value because {_2} is a tag` local why = `cannot set a component value because {_2} is a tag`
why ..= `\n[jecs] note: consider using "world:add({_1}, {_2})" instead` why ..= `\n[jecs] note: consider using "world:add({_1}, {_2})" instead`
throw(why) throw(why)
return return
end end
world_set(world, entity, id, value) world_set(world, entity, id, value)
end end
World.add = function(world: World, entity: i53, id: i53, value: any) World.add = function(world: World, entity: i53, id: i53, value: any)
if value ~= nil then if value ~= nil then
local _1 = bname(world, entity) local _1 = bname(world, entity)
local _2 = bname(world, id) local _2 = bname(world, id)
throw("You provided a value when none was expected. " .. `Did you mean to use "world:add({_1}, {_2})"`) throw("You provided a value when none was expected. " .. `Did you mean to use "world:add({_1}, {_2})"`)
end end
world_add(world, entity, id) world_add(world, entity, id)
end end
World.get = function(world: World, entity: i53, ...) World.get = function(world: World, entity: i53, ...)
local length = select("#", ...) local length = select("#", ...)
ASSERT(length < 5, "world:get does not support more than 4 components") ASSERT(length < 5, "world:get does not support more than 4 components")
local _1 local _1
for i = 1, length do for i = 1, length do
local id = select(i, ...) local id = select(i, ...)
local id_is_tag = not world_has(world, id, EcsComponent) local id_is_tag = not world_has(world, id, EcsComponent)
if id_is_tag then if id_is_tag then
local name = get_name(world, id) local name = get_name(world, id)
if not _1 then if not _1 then
_1 = get_name(world, entity) _1 = get_name(world, entity)
end end
throw( throw(
`cannot get (#{i}) component {name} value because it is a tag.` `cannot get (#{i}) component {name} value because it is a tag.`
.. `\n[jecs] note: If this was intentional, use "world:has({_1}, {name}) instead"` .. `\n[jecs] note: If this was intentional, use "world:has({_1}, {name}) instead"`
) )
end end
end end
return world_get(world, entity, ...) return world_get(world, entity, ...)
end end
end end

View file

@ -534,7 +534,7 @@ return {
FINISH = FINISH, FINISH = FINISH,
SKIP = SKIP, SKIP = SKIP,
FOCUS = FOCUS, FOCUS = FOCUS,
CHECK_EXPECT_ERR = CHECK_EXPECT_ERR CHECK_EXPECT_ERR = CHECK_EXPECT_ERR,
} }
end, end,