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
about: File a bug report for any behavior that you believe is unintentional or problematic
title: "[BUG]"
labels: bug
assignees: ''
---
## 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.
## 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.
This is a good place to put rbxl files or scripts that help explain your reproduction steps.
## Expected Behavior
What you expect to happen
## Actual Behavior
What actually happens
---
name: Bug report
about: File a bug report for any behavior that you believe is unintentional or problematic
title: ""
labels: bug
assignees: ""
---
## 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.
## 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.
This is a good place to put rbxl files or scripts that help explain your reproduction steps.
## Expected Behavior
What you expect to happen
## Actual Behavior
What actually happens

View file

@ -1,14 +1,15 @@
---
name: Documentation
about: Open an issue to add, change, or otherwise modify any part of the documentation.
title: "[DOCS]"
title: ""
labels: documentation
assignees: ''
assignees: ""
---
## Which Sections Does This Issue Cover?
[Put sections (e.g. Query Concepts), page links, etc as necessary]
## 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
about: File a feature request for something you believe should be added to Jecs
title: "[FEATURE]"
labels: enhancement
assignees: ''
---
## Describe your Feature
You should explain your feature here, and the motivation for why you want it.
## Implementation
Explain how you would implement your feature here. Provide relevant API examples and such here (if applicable).
## Alternatives
What other alternative implementations or otherwise relevant information is important to why you decided to go with this specific implementation?
## Considerations
Some questions that need to be answered include the following:
- Will old code break in response to this feature?
- What are the performance impacts with this feature (if any)?
- How is it useful to include?
---
name: Feature Request
about: File a feature request for something you believe should be added to Jecs
title: ""
labels: enhancement
assignees: ""
---
## Describe your Feature
You should explain your feature here, and the motivation for why you want it.
## Implementation
Explain how you would implement your feature here. Provide relevant API examples and such here (if applicable).
## Alternatives
What other alternative implementations or otherwise relevant information is important to why you decided to go with this specific implementation?
## Considerations
Some questions that need to be answered include the following:
- Will old code break in response to this feature?
- What are the performance impacts with this feature (if any)?
- How is it useful to include?

View file

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

View file

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

View file

@ -1,49 +1,49 @@
--!optimize 2
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.DevPackages.Matter)
local ecr = require(ReplicatedStorage.DevPackages.ecr)
local jecs = require(ReplicatedStorage.Lib)
local pair = jecs.pair
local ecs = jecs.World.new()
local mirror = require(ReplicatedStorage.mirror)
local mcs = mirror.World.new()
local C1 = ecs:component()
local C2 = ecs:entity()
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
local C3 = ecs:entity()
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
local C4 = ecs:entity()
ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete))
local E1 = mcs:component()
local E2 = mcs:entity()
mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete))
local E3 = mcs:entity()
mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete))
local E4 = mcs:entity()
mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete))
return {
ParameterGenerator = function()
end,
Functions = {
Mirror = function()
local m = mcs:entity()
for i = 1, 100 do
mcs:add(m, E3)
mcs:remove(m, E3)
end
end,
Jecs = function()
local j = ecs:entity()
for i = 1, 100 do
ecs:add(j, C3)
ecs:remove(j, C3)
end
end,
},
}
--!optimize 2
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.DevPackages.Matter)
local ecr = require(ReplicatedStorage.DevPackages.ecr)
local jecs = require(ReplicatedStorage.Lib)
local pair = jecs.pair
local ecs = jecs.World.new()
local mirror = require(ReplicatedStorage.mirror)
local mcs = mirror.World.new()
local C1 = ecs:component()
local C2 = ecs:entity()
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
local C3 = ecs:entity()
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
local C4 = ecs:entity()
ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete))
local E1 = mcs:component()
local E2 = mcs:entity()
mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete))
local E3 = mcs:entity()
mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete))
local E4 = mcs:entity()
mcs:add(E4, pair(jecs.OnDeleteTarget, jecs.Delete))
return {
ParameterGenerator = function()
end,
Functions = {
Mirror = function()
local m = mcs:entity()
for i = 1, 100 do
mcs:add(m, E3)
mcs:remove(m, E3)
end
end,
Jecs = function()
local j = ecs:entity()
for i = 1, 100 do
ecs:add(j, C3)
ecs:remove(j, C3)
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 function trackers_invoke(event, component, entity, ...)
local trackers = events[event][component]
if not trackers then
return
end
for _, tracker in trackers do
tracker(entity, data)
end
end
local function trackers_init(event, component, fn)
local ob = events[event]
return {
connect = function(component, fn)
local trackers = ob[component]
if not trackers then
trackers = {}
ob[component] = trackers
end
table.insert(trackers, fn)
end,
invoke = function(component, ...)
trackers_invoke(event, component, ...)
end
}
return function(component, fn)
local trackers = ob[component]
if not trackers then
trackers = {}
ob[component] = trackers
end
table.insert(trackers, fn)
end
end
local trackers = {
emplace = trackers_init("emplace"),
add = trackers_init("added"),
remove = trackers_init("removed")
}
return trackers
local events = {}
local function trackers_invoke(event, component, entity, ...)
local trackers = events[event][component]
if not trackers then
return
end
for _, tracker in trackers do
tracker(entity, data)
end
end
local function trackers_init(event, component, fn)
local ob = events[event]
return {
connect = function(component, fn)
local trackers = ob[component]
if not trackers then
trackers = {}
ob[component] = trackers
end
table.insert(trackers, fn)
end,
invoke = function(component, ...)
trackers_invoke(event, component, ...)
end
}
return function(component, fn)
local trackers = ob[component]
if not trackers then
trackers = {}
ob[component] = trackers
end
table.insert(trackers, fn)
end
end
local trackers = {
emplace = trackers_init("emplace"),
add = trackers_init("added"),
remove = trackers_init("removed")
}
return trackers

View file

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

View file

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

View file

@ -52,7 +52,7 @@ export type Archetype = {
type ecs_record_t = {
archetype: ecs_archetype_t,
row: number,
dense: i24
dense: i24,
}
type ecs_id_record_t = {
@ -90,8 +90,8 @@ type ecs_query_data_t = {
}
type ecs_observer_t = {
callback: (archetype: ecs_archetype_t) -> (),
query: ecs_query_data_t,
callback: (archetype: ecs_archetype_t) -> (),
query: ecs_query_data_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)
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,
archetype: ecs_archetype_t)
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 is_pair = ECS_IS_PAIR(id)
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(
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(
entity_index, target), ECS_INTERNAL_ERROR)
end
@ -646,7 +642,6 @@ local function archetype_create(world: ecs_world_t, id_types: { i24 }, ty, prev:
local records: { number } = {}
local counts: {number} = {}
local entity_index = world.entity_index
local archetype: ecs_archetype_t = {
columns = columns,
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"
rojo = "rojo-rbx/rojo@7.4.4"
stylua = "johnnymorganz/stylua@2.0.1"
selene = "kampfkarren/selene@0.27.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 pair = jecs.pair
local ChildOf = jecs.ChildOf
local lifetime_tracker_add = require("@tools/lifetime_tracker")
local pe = require("@tools/entity_visualiser").prettify
local world = lifetime_tracker_add(jecs.world(), {padding_enabled=false})
local FriendsWith = world:component()
world:print_snapshot()
local e1 = world:entity()
local e2 = world:entity()
world:delete(e2)
world:print_snapshot()
local e3 = world:entity()
world:add(e3, pair(ChildOf, e1))
local e4 = world:entity()
world:add(e4, pair(FriendsWith, e3))
world:print_snapshot()
world:delete(e1)
world:delete(e3)
world:print_snapshot()
world:print_entity_index()
world:entity()
world:entity()
world:print_snapshot()
local jecs = require("@jecs")
local pair = jecs.pair
local ChildOf = jecs.ChildOf
local lifetime_tracker_add = require("@tools/lifetime_tracker")
local pe = require("@tools/entity_visualiser").prettify
local world = lifetime_tracker_add(jecs.world(), {padding_enabled=false})
local FriendsWith = world:component()
world:print_snapshot()
local e1 = world:entity()
local e2 = world:entity()
world:delete(e2)
world:print_snapshot()
local e3 = world:entity()
world:add(e3, pair(ChildOf, e1))
local e4 = world:entity()
world:add(e4, pair(FriendsWith, e3))
world:print_snapshot()
world:delete(e1)
world:delete(e3)
world:print_snapshot()
world:print_entity_index()
world:entity()
world:entity()
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 = {
white_underline = function(s: any)
return `\27[1;4m{s}\27[0m`
end,
white = function(s: any)
return `\27[37;1m{s}\27[0m`
end,
green = function(s: any)
return `\27[32;1m{s}\27[0m`
end,
red = function(s: any)
return `\27[31;1m{s}\27[0m`
end,
yellow = function(s: any)
return `\27[33;1m{s}\27[0m`
end,
red_highlight = function(s: any)
return `\27[41;1;30m{s}\27[0m`
end,
green_highlight = function(s: any)
return `\27[42;1;30m{s}\27[0m`
end,
gray = function(s: any)
return `\27[30;1m{s}\27[0m`
end,
}
local ECS_PAIR_FLAG = 0x8
local ECS_ID_FLAGS_MASK = 0x10
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
type i53 = number
type i24 = number
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
end
local function ECS_GENERATION(e: i53): i24
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_GENERATION_MASK else 0
end
local ECS_ID = ECS_ENTITY_T_LO
local function ECS_COMBINE(source: number, target: number): i53
return (source * 268435456) + (target * ECS_ID_FLAGS_MASK)
end
local function ECS_GENERATION_INC(e: i53)
if e > ECS_ENTITY_MASK then
local flags = e // ECS_ID_FLAGS_MASK
local id = flags // ECS_ENTITY_MASK
local generation = flags % ECS_GENERATION_MASK
local next_gen = generation + 1
if next_gen > ECS_GENERATION_MASK then
return id
end
return ECS_COMBINE(id, next_gen) + flags
end
return ECS_COMBINE(e, 1)
end
local function bl()
print("")
end
local function pe(e)
local gen = ECS_GENERATION(e)
return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`)
end
local function dprint(tbl: { [number]: number })
bl()
print("--------")
for i, e in tbl do
print("| "..pe(e).." |")
print("--------")
end
bl()
end
local max_id = 0
local alive_count = 0
local dense = {}
local sparse = {}
local function alloc()
if alive_count ~= #dense then
alive_count += 1
print("*recycled", pe(dense[alive_count]))
return dense[alive_count]
end
max_id += 1
local id = max_id
alive_count += 1
dense[alive_count] = id
sparse[id] = {
dense = alive_count
}
print("*allocated", pe(id))
return id
end
local function remove(entity)
local id = ECS_ID(entity)
local r = sparse[id]
local index_of_deleted_entity = r.dense
local last_entity_alive_at_index = alive_count -- last entity alive
alive_count -= 1
local last_alive_entity = dense[last_entity_alive_at_index]
local r_swap = sparse[ECS_ID(last_alive_entity)]
r_swap.dense = r.dense
r.dense = last_entity_alive_at_index
dense[index_of_deleted_entity] = last_alive_entity
dense[last_entity_alive_at_index] = ECS_GENERATION_INC(entity)
print("*dellocated", pe(id))
end
local function alive(e)
local r = sparse[ECS_ID(e)]
return dense[r.dense] == e
end
local function pa(e)
print(`{pe(e)} is {if alive(e) then "alive" else "not alive"}`)
end
local tprint = require("@testkit").print
local e1v0 = alloc()
local e2v0 = alloc()
local e3v0 = alloc()
local e4v0 = alloc()
local e5v0 = alloc()
pa(e1v0)
pa(e4v0)
remove(e5v0)
pa(e5v0)
local e5v1 = alloc()
pa(e5v0)
pa(e5v1)
pa(e2v0)
print(ECS_ID(e2v0))
dprint(dense)
remove(e2v0)
dprint(dense)
local c = {
white_underline = function(s: any)
return `\27[1;4m{s}\27[0m`
end,
white = function(s: any)
return `\27[37;1m{s}\27[0m`
end,
green = function(s: any)
return `\27[32;1m{s}\27[0m`
end,
red = function(s: any)
return `\27[31;1m{s}\27[0m`
end,
yellow = function(s: any)
return `\27[33;1m{s}\27[0m`
end,
red_highlight = function(s: any)
return `\27[41;1;30m{s}\27[0m`
end,
green_highlight = function(s: any)
return `\27[42;1;30m{s}\27[0m`
end,
gray = function(s: any)
return `\27[30;1m{s}\27[0m`
end,
}
local ECS_PAIR_FLAG = 0x8
local ECS_ID_FLAGS_MASK = 0x10
local ECS_ENTITY_MASK = bit32.lshift(1, 24)
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
type i53 = number
type i24 = number
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
end
local function ECS_GENERATION(e: i53): i24
return if e > ECS_ENTITY_MASK then (e // ECS_ID_FLAGS_MASK) % ECS_GENERATION_MASK else 0
end
local ECS_ID = ECS_ENTITY_T_LO
local function ECS_COMBINE(source: number, target: number): i53
return (source * 268435456) + (target * ECS_ID_FLAGS_MASK)
end
local function ECS_GENERATION_INC(e: i53)
if e > ECS_ENTITY_MASK then
local flags = e // ECS_ID_FLAGS_MASK
local id = flags // ECS_ENTITY_MASK
local generation = flags % ECS_GENERATION_MASK
local next_gen = generation + 1
if next_gen > ECS_GENERATION_MASK then
return id
end
return ECS_COMBINE(id, next_gen) + flags
end
return ECS_COMBINE(e, 1)
end
local function bl()
print("")
end
local function pe(e)
local gen = ECS_GENERATION(e)
return c.green(`e{ECS_ID(e)}`)..c.yellow(`v{gen}`)
end
local function dprint(tbl: { [number]: number })
bl()
print("--------")
for i, e in tbl do
print("| "..pe(e).." |")
print("--------")
end
bl()
end
local max_id = 0
local alive_count = 0
local dense = {}
local sparse = {}
local function alloc()
if alive_count ~= #dense then
alive_count += 1
print("*recycled", pe(dense[alive_count]))
return dense[alive_count]
end
max_id += 1
local id = max_id
alive_count += 1
dense[alive_count] = id
sparse[id] = {
dense = alive_count
}
print("*allocated", pe(id))
return id
end
local function remove(entity)
local id = ECS_ID(entity)
local r = sparse[id]
local index_of_deleted_entity = r.dense
local last_entity_alive_at_index = alive_count -- last entity alive
alive_count -= 1
local last_alive_entity = dense[last_entity_alive_at_index]
local r_swap = sparse[ECS_ID(last_alive_entity)]
r_swap.dense = r.dense
r.dense = last_entity_alive_at_index
dense[index_of_deleted_entity] = last_alive_entity
dense[last_entity_alive_at_index] = ECS_GENERATION_INC(entity)
print("*dellocated", pe(id))
end
local function alive(e)
local r = sparse[ECS_ID(e)]
return dense[r.dense] == e
end
local function pa(e)
print(`{pe(e)} is {if alive(e) then "alive" else "not alive"}`)
end
local tprint = require("@testkit").print
local e1v0 = alloc()
local e2v0 = alloc()
local e3v0 = alloc()
local e4v0 = alloc()
local e5v0 = alloc()
pa(e1v0)
pa(e4v0)
remove(e5v0)
pa(e5v0)
local e5v1 = alloc()
pa(e5v0)
pa(e5v1)
pa(e2v0)
print(ECS_ID(e2v0))
dprint(dense)
remove(e2v0)
dprint(dense)

View file

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

View file

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

View file

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

View file

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

View file

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