R
roblox-ts•3mo ago
Braden

More efficient ways to JECS Query

I have an query using JECS that runs my NPC behavior. The goal is to select a random point on the map to roam too.
const valids: CFrame[] = [];
for (const [entity, transform] of world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)) {
valids.push(transform);
}
if (valids.size() <= 0) return StepStatus.Failed;
const newRaw = valids[math.random(0, valids.size() - 1)];
const valids: CFrame[] = [];
for (const [entity, transform] of world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)) {
valids.push(transform);
}
if (valids.size() <= 0) return StepStatus.Failed;
const newRaw = valids[math.random(0, valids.size() - 1)];
this seems very slow, is it possible to just grab a table of transform components with a TycoonTags.floor tag? My immediate thought for now is to implement cacheing because right now I am doing this 20 times a second šŸ’€ Suggestions would be appreciated šŸ™ !
Solution:
```ts const archetypes = query.archetypes() for(const archetype of archetypes){ const index = archetype.records[transform]...
Jump to solution
123 Replies
PepeElToro41
PepeElToro41•3mo ago
yes, you can directly get the archetypes and then you'd get them as a simple array which is cool I dont know of roblox-ts has any of these types though it's more internal
Braden
BradenOP•3mo ago
oh is that what .archetypes() does?
const transforms = world.query(sharedComponents.Transform).with(sharedComponents.TycoonTags.floor).archetypes();
const transforms = world.query(sharedComponents.Transform).with(sharedComponents.TycoonTags.floor).archetypes();
PepeElToro41
PepeElToro41•3mo ago
not quite, you would get the archetypes
PepeElToro41
PepeElToro41•3mo ago
No description
PepeElToro41
PepeElToro41•3mo ago
then you iterate all the archetypes for each archetype, you can get where the transform is by doing archetype.records[transform] then you can directly access the array in colums[archetype.records[transform]]
Braden
BradenOP•3mo ago
I see, something like this:
const transforms = world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)
.archetypes();
const randomTransform = transforms[math.random(0, transforms.size() - 1)];
const transform = randomTransform.columns[randomTransform.records[sharedComponents.Transform]][0];
const transforms = world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)
.archetypes();
const randomTransform = transforms[math.random(0, transforms.size() - 1)];
const transform = randomTransform.columns[randomTransform.records[sharedComponents.Transform]][0];
it would cut out the extra iteration
Solution
PepeElToro41
PepeElToro41•3mo ago
const archetypes = query.archetypes()

for(const archetype of archetypes){
const index = archetype.records[transform]
const transformsArray = archetype.colums[index]

print(transformsArray) // CFrame[]
}
const archetypes = query.archetypes()

for(const archetype of archetypes){
const index = archetype.records[transform]
const transformsArray = archetype.colums[index]

print(transformsArray) // CFrame[]
}
Braden
BradenOP•3mo ago
I see
PepeElToro41
PepeElToro41•3mo ago
you would still get different arrays
Braden
BradenOP•3mo ago
thats really cool
PepeElToro41
PepeElToro41•3mo ago
yeah so after that I guess you can iterate all the arrays, get the combined size of all of them and use that for the random and to access the random you will need some simple maths, should be straight forward
Braden
BradenOP•3mo ago
didnt realize there was also a counts proeprty too, thats going to really help speed up the counting I do Thank you!!! Hope this will fix the long execution times in script profiler
PepeElToro41
PepeElToro41•3mo ago
I'd recommend printing the archetype, I believe this is how the internals are setup but I might be wrong XD but the idea is the same
Braden
BradenOP•3mo ago
I ended up having to do const index = archetype.records[sharedComponents.Transform - 1]; because in lua its adding +1, however the reuslt is not a list of CFrames its actually a different component, one that I never spcified in the query:
for (const archetype of world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)
.archetypes()) {
const index = archetype.records[sharedComponents.Transform - 1];
const transformsArray = archetype.columns[index];

print(transformsArray); // String[] when it should be CFrame[] (its pulling a completely different component than transform)
}
for (const archetype of world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)
.archetypes()) {
const index = archetype.records[sharedComponents.Transform - 1];
const transformsArray = archetype.columns[index];

print(transformsArray); // String[] when it should be CFrame[] (its pulling a completely different component than transform)
}
do you have any thoughts on what might cause this?
PepeElToro41
PepeElToro41•3mo ago
shouldnt it be transform + 1?
Braden
BradenOP•3mo ago
no + 1 ended up making index = nil this is what records looks like
{
[2] = 1,
[25] = 2,
[27] = 3,
[30] = 4,
[31] = 5,
[45] = 6,
[52] = 7,
[304] = 8
}
{
[2] = 1,
[25] = 2,
[27] = 3,
[30] = 4,
[31] = 5,
[45] = 6,
[52] = 7,
[304] = 8
}
and sharedComponents.Transform === 2 but in lua it +1 so I have had to -1 in typescript, but the [2] = 1 gets a different value
PepeElToro41
PepeElToro41•3mo ago
oh I see, records is a map I imagine right
Braden
BradenOP•3mo ago
in types records is a number[]
PepeElToro41
PepeElToro41•3mo ago
its an array yeah, so, when getting the records you'd do +1 and to get the colum you would do -1 I think actually no, when getting the records you do +1, but when getting the colum you wouldnt do anything because that's a real array
Braden
BradenOP•3mo ago
but when getting the record with +1, it ends up being nil which causes the column look up to fail
PepeElToro41
PepeElToro41•3mo ago
what's the transform id?
Braden
BradenOP•3mo ago
2 but when i print out the columns its actually on 1? i think { [1] = ā–¼ { [1] = -825.800049, -267.129486, -1344.05005, 1, 0, 0, 0, 1, 0, 0, 0, 1, } theres more than 1 transform, just truncated for message
PepeElToro41
PepeElToro41•3mo ago
then I think you shouldnt add anything or substract anything
Braden
BradenOP•3mo ago
then that leads to lua adding +1 and the index will be undefined
PepeElToro41
PepeElToro41•3mo ago
https://github.com/Ukendio/jecs/blob/main/jecs.luau#L477 I think it's just roblox-ts arrays messing it up
Braden
BradenOP•3mo ago
ah
PepeElToro41
PepeElToro41•3mo ago
yeah so I got confused, this seems correct, its pointing to 1, and the colums for transform are in 1 but here after trying to index 1, you would index 2 because it would add one, so you need to substract one in both cases
for (const archetype of world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)
.archetypes()) {
const index = archetype.records[sharedComponents.Transform - 1];
const transformsArray = archetype.columns[index - 1];

print(transformsArray);
}
for (const archetype of world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)
.archetypes()) {
const index = archetype.records[sharedComponents.Transform - 1];
const transformsArray = archetype.columns[index - 1];

print(transformsArray);
}
Braden
BradenOP•3mo ago
šŸ™ that was it! thank you so much
PepeElToro41
PepeElToro41•3mo ago
typescript arrays are confusing or actually luau arrays are confusing
Braden
BradenOP•3mo ago
yeah I used to be a lua lover, then I learned typescript 🤣 i cant stand typing or using lua queries went from 0.5ms to 0.005ms and below so massive difference when I have like 500 npcs running at once šŸŽ‰
PepeElToro41
PepeElToro41•3mo ago
0.5ms is quite a lot šŸ’€
Braden
BradenOP•3mo ago
well yeah when you multiply it by every npc, now I cant wait to revisit every query and test the script profiler learning jecs while making the game so theres plenty of agony to go arround
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
What specifically do you want to see?
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
I’m at work Give me a couple hours
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
let valids: CFrame[] = [];

for (const archetype of world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)
.archetypes()) {
const index = archetype.records[sharedComponents.Transform - 1];
const transformsArray = archetype.columns[index - 1];
valids = [...valids, ...(transformsArray as CFrame[])];
}

if (valids.size() <= 0) return StepStatus.Failed;
const newRaw = valids[math.random(0, valids.size() - 1)];
performPathfindingV3(world, entityID, newRaw);
let valids: CFrame[] = [];

for (const archetype of world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)
.archetypes()) {
const index = archetype.records[sharedComponents.Transform - 1];
const transformsArray = archetype.columns[index - 1];
valids = [...valids, ...(transformsArray as CFrame[])];
}

if (valids.size() <= 0) return StepStatus.Failed;
const newRaw = valids[math.random(0, valids.size() - 1)];
performPathfindingV3(world, entityID, newRaw);
PepeElToro41
PepeElToro41•3mo ago
isnt this cloning the arrays anyway? I thought you would just randomly grab one from the current arrays
Braden
BradenOP•3mo ago
not like before, i was interating unnecessarily to clone the arrays and filter out things this way has way less iterations involved
PepeElToro41
PepeElToro41•3mo ago
hmm I see I think you could do it without cloning the arrays
Braden
BradenOP•3mo ago
How so? I still have to piece them all together
PepeElToro41
PepeElToro41•3mo ago
something like this
let size = 0
for (const archetype of archetypes()) {
column = archetypes.colums[] // get column
size += column.size()
}

let target = math.random(0, size - 1)
let random = undefined

for (const archetype of archetypes()) {
column = archetypes.colums[] // get column
if (target < column.size()) {
random = column[target]
break
}
target -= colum.size()
}
let size = 0
for (const archetype of archetypes()) {
column = archetypes.colums[] // get column
size += column.size()
}

let target = math.random(0, size - 1)
let random = undefined

for (const archetype of archetypes()) {
column = archetypes.colums[] // get column
if (target < column.size()) {
random = column[target]
break
}
target -= colum.size()
}
needs two iterations but it wont clone arrays
Braden
BradenOP•3mo ago
There could be over like 5k floor entities so iterating isnt the best
PepeElToro41
PepeElToro41•3mo ago
you are not iterating you are just iterating the archetypes note that you are cloning arrays
Braden
BradenOP•3mo ago
Oh wait I see
PepeElToro41
PepeElToro41•3mo ago
which is iterating
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
PepeElToro41
PepeElToro41•3mo ago
second of all? did I give bad advice :Babbyblob:
Braden
BradenOP•3mo ago
:WOAH: ahh, just by assigning it to a local variable?
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
PepeElToro41
PepeElToro41•3mo ago
like this:
const query = world.query(sharedComponents.Transform).with(sharedComponents.TycoonTags.floor).cached()
const query = world.query(sharedComponents.Transform).with(sharedComponents.TycoonTags.floor).cached()
Braden
BradenOP•3mo ago
thanks! i appreciate the advice ya'll, Im still learning this ECS stuff šŸ™‚
PepeElToro41
PepeElToro41•3mo ago
same
Braden
BradenOP•3mo ago
my game is a little ambitious so i have to put a lot of thought into optimization
PepeElToro41
PepeElToro41•3mo ago
yeah this should have almost a constant time complexity so it should be pretty fast doesnt matter how many you have I think #array is constant in luau because it gets cached
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
that must be why stuff like this is really expensive in the profiler:
for (const [entityID, npc, transform, targeting] of world
.query(sharedComponents.NPC, sharedComponents.Transform, sharedComponents.Targeting)
.without(sharedComponents.PathfindingComponent)) {}
for (const [entityID, npc, transform, targeting] of world
.query(sharedComponents.NPC, sharedComponents.Transform, sharedComponents.Targeting)
.without(sharedComponents.PathfindingComponent)) {}
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
ayy! thanks for the heads up!\
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
I have like 10 others just like this šŸ’€ thank you lol! so something like this would be better practice?
function npcsMoveToTargets(world: World) {
const npcsTargeting = world
.query(sharedComponents.NPC, sharedComponents.Transform, sharedComponents.Targeting)
.without(sharedComponents.PathfindingComponent)
.cached();
for (const [entityID, npc, transform, targeting] of npcsTargeting) {
// targeting, lets move to them!
}
}
function npcsMoveToTargets(world: World) {
const npcsTargeting = world
.query(sharedComponents.NPC, sharedComponents.Transform, sharedComponents.Targeting)
.without(sharedComponents.PathfindingComponent)
.cached();
for (const [entityID, npc, transform, targeting] of npcsTargeting) {
// targeting, lets move to them!
}
}
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
oh, well that might be an issue because I am using Planck as a scheduler and it relies on that world
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
does returning a function work with planck add systems? I wasnt aware of that in any documentation
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
switching over now would be time consuming so hopefully there is a way
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
ah, got it
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
i have a little bit of a weird use case. When players load a tycoon it creates a new world every time and then cleans it up new world + scheduler ^
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
so relying on a shared world in a module wouldn't work for me right now without restructuring i could potential pass in a table into the system like I do with a Maid already
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
If you can be more specific about what you want to see, sure
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
What about the transform? I am confused
worldMaid.GiveTask(
world.changed(sharedComponents.Transform, (entity) => {
const smoothing = world.has(entity, sharedComponents.SmoothTransform);
const transform = world.get(entity, sharedComponents.Transform);
if (transform !== undefined) {
if (world.has(entity, sharedComponents.Model)) {
const model = world.get(entity, sharedComponents.Model);
if (model !== undefined && model.IsDescendantOf(Workspace)) {
if (smoothing) {
if (model.PrimaryPart) {
TweenService.Create(
model.PrimaryPart,
new TweenInfo(1 / 10, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut),
{ CFrame: transform.mul(model.PrimaryPart.PivotOffset.Inverse()) },
).Play();
}
} else {
(model as Model).PivotTo(transform as CFrame);
}
}
}
if (world.has(entity, sharedComponents.Attachment)) {
const attachment = world.get(entity, sharedComponents.Attachment);
if (attachment !== undefined) {
attachment.WorldCFrame = transform as CFrame;
}
}
}
}),
);
worldMaid.GiveTask(
world.changed(sharedComponents.Transform, (entity) => {
const smoothing = world.has(entity, sharedComponents.SmoothTransform);
const transform = world.get(entity, sharedComponents.Transform);
if (transform !== undefined) {
if (world.has(entity, sharedComponents.Model)) {
const model = world.get(entity, sharedComponents.Model);
if (model !== undefined && model.IsDescendantOf(Workspace)) {
if (smoothing) {
if (model.PrimaryPart) {
TweenService.Create(
model.PrimaryPart,
new TweenInfo(1 / 10, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut),
{ CFrame: transform.mul(model.PrimaryPart.PivotOffset.Inverse()) },
).Play();
}
} else {
(model as Model).PivotTo(transform as CFrame);
}
}
}
if (world.has(entity, sharedComponents.Attachment)) {
const attachment = world.get(entity, sharedComponents.Attachment);
if (attachment !== undefined) {
attachment.WorldCFrame = transform as CFrame;
}
}
}
}),
);
if this is what your asking for, this is how I update workspace based on transform
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
it does pathfinding
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
yeah sure- just lead with that next time its a helper function
export function performPathfindingV3(
world: World,
entity: Entity<unknown>,
target: CFrame,
pathfindTo?: CFrame,
showFail?: boolean,
) {
const tycoonEntity = getTycoon(world);
if (tycoonEntity === undefined) {
world.add(entity, sharedComponents.PathfindingStatus.Failed as Entity<undefined>);
return false;
}
const tycoon = world.get(tycoonEntity, sharedComponents.Tycoon);
if (tycoon === undefined) {
world.add(entity, sharedComponents.PathfindingStatus.Failed as Entity<undefined>);
return false;
}
const tycoonModel = world.get(tycoonEntity, sharedComponents.Model);
if (tycoonModel === undefined) {
world.add(entity, sharedComponents.PathfindingStatus.Failed as Entity<undefined>);
return false;
}

const transform = world.get(entity, sharedComponents.Transform);
if (!transform) {
world.add(entity, sharedComponents.PathfindingStatus.Failed as Entity<undefined>);
return false;
}

world.remove(entity, sharedComponents.PathfindingStatus.Success);
world.remove(entity, sharedComponents.PathfindingStatus.Failed);
world.remove(entity, sharedComponents.PathfindingStatus.ClosestNoPath);

world.set(entity, sharedComponents.PathfindPending, { target: target, pathfindTo: pathfindTo, showFail: showFail });
return true;
}
export function performPathfindingV3(
world: World,
entity: Entity<unknown>,
target: CFrame,
pathfindTo?: CFrame,
showFail?: boolean,
) {
const tycoonEntity = getTycoon(world);
if (tycoonEntity === undefined) {
world.add(entity, sharedComponents.PathfindingStatus.Failed as Entity<undefined>);
return false;
}
const tycoon = world.get(tycoonEntity, sharedComponents.Tycoon);
if (tycoon === undefined) {
world.add(entity, sharedComponents.PathfindingStatus.Failed as Entity<undefined>);
return false;
}
const tycoonModel = world.get(tycoonEntity, sharedComponents.Model);
if (tycoonModel === undefined) {
world.add(entity, sharedComponents.PathfindingStatus.Failed as Entity<undefined>);
return false;
}

const transform = world.get(entity, sharedComponents.Transform);
if (!transform) {
world.add(entity, sharedComponents.PathfindingStatus.Failed as Entity<undefined>);
return false;
}

world.remove(entity, sharedComponents.PathfindingStatus.Success);
world.remove(entity, sharedComponents.PathfindingStatus.Failed);
world.remove(entity, sharedComponents.PathfindingStatus.ClosestNoPath);

world.set(entity, sharedComponents.PathfindPending, { target: target, pathfindTo: pathfindTo, showFail: showFail });
return true;
}
and then it calls a pathfinding module i built it copying mostly from this awesome algorithm https://love2d.org/forums/viewtopic.php?t=90395.
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
FindingRoamPoint: {
name: "FindingRoamPoint",
taskName: "Finding Roam Point",
execute: (world: World, entityID: Entity<unknown>) => {
const tycoonID = getTycoon(world);
if (tycoonID === undefined) return StepStatus.Failed;
const tycoonModel = world.get(tycoonID, sharedComponents.Model);
if (!tycoonModel) return StepStatus.Failed;

const tycoon = world.get(tycoonID, sharedComponents.Tycoon);
if (!tycoon) return StepStatus.Failed;

// Only proceed if entity has a Transform
const transform = world.get(entityID, sharedComponents.Transform);
if (!transform) return StepStatus.Failed;

const platform = getPlatformByCFrame(tycoonModel, transform);
if (!platform) return StepStatus.Failed;

let valids: CFrame[] = [];
// for (const [entity, transform] of world
// .query(sharedComponents.Transform)
// .with(sharedComponents.TycoonTags.floor)
// .cached()) {
// valids.push(transform);
// }
// if (valids.size() <= 0) return StepStatus.Failed;

for (const archetype of world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)
.archetypes()) {
const index = archetype.records[sharedComponents.Transform - 1];
const transformsArray = archetype.columns[index - 1];
valids = [...valids, ...(transformsArray as CFrame[])];
}

if (valids.size() <= 0) return StepStatus.Failed;
const newRaw = valids[math.random(0, valids.size() - 1)];
performPathfindingV3(world, entityID, newRaw);
return StepStatus.Success;
},

next: "WalkingToRoamPoint",
fallback: undefined,
},
FindingRoamPoint: {
name: "FindingRoamPoint",
taskName: "Finding Roam Point",
execute: (world: World, entityID: Entity<unknown>) => {
const tycoonID = getTycoon(world);
if (tycoonID === undefined) return StepStatus.Failed;
const tycoonModel = world.get(tycoonID, sharedComponents.Model);
if (!tycoonModel) return StepStatus.Failed;

const tycoon = world.get(tycoonID, sharedComponents.Tycoon);
if (!tycoon) return StepStatus.Failed;

// Only proceed if entity has a Transform
const transform = world.get(entityID, sharedComponents.Transform);
if (!transform) return StepStatus.Failed;

const platform = getPlatformByCFrame(tycoonModel, transform);
if (!platform) return StepStatus.Failed;

let valids: CFrame[] = [];
// for (const [entity, transform] of world
// .query(sharedComponents.Transform)
// .with(sharedComponents.TycoonTags.floor)
// .cached()) {
// valids.push(transform);
// }
// if (valids.size() <= 0) return StepStatus.Failed;

for (const archetype of world
.query(sharedComponents.Transform)
.with(sharedComponents.TycoonTags.floor)
.archetypes()) {
const index = archetype.records[sharedComponents.Transform - 1];
const transformsArray = archetype.columns[index - 1];
valids = [...valids, ...(transformsArray as CFrame[])];
}

if (valids.size() <= 0) return StepStatus.Failed;
const newRaw = valids[math.random(0, valids.size() - 1)];
performPathfindingV3(world, entityID, newRaw);
return StepStatus.Success;
},

next: "WalkingToRoamPoint",
fallback: undefined,
},
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
.
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
Was working last night Their host must be down I used an a star algorithm
Braden
BradenOP•3mo ago
GitHub
GitHub - GlorifiedPig/Luafinding: Fast & easy-to-use A* module writ...
Fast & easy-to-use A* module written in Lua. Contribute to GlorifiedPig/Luafinding development by creating an account on GitHub.
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
Idk Different probably
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
Here it is Literally copied it šŸ˜‰ All i did is generate some types with chat gpt
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
I have a question about this, I just tried using cache with a query using with and without, the behavior I noticed is that if an entity has those dependents changes, say the query is world.query(transform).cached() if transform is removed, the query still runs as it has not removed. Does this mean I need to perodically (when something worthy changes), re-make the cache? Do I have that right?
Braden
BradenOP•3mo ago
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
it would not be the first time I did something wrong! it was probably an issue with me passing in a big table of cached queries into planck, I will probably slowly move over to a different scheduler which scheduler do you use? is it the same as the one in the demo?
PepeElToro41
PepeElToro41•3mo ago
I use that one very recommended šŸ‘ no planck bloat
Braden
BradenOP•3mo ago
ill check it out, is it jabby's scheduler? Last time I saw thats what it looked like it called :run on jabby
PepeElToro41
PepeElToro41•3mo ago
no jabby scheduler is just an interface used so jabby can time/disable your schedulers
Braden
BradenOP•3mo ago
but it supports returning a function as the main system?
PepeElToro41
PepeElToro41•3mo ago
so instead of calling your systems once per frame you do jabby.scheduler:run(system) but jabby itself is not a scheduler, its a bad name you'd still need to call this per frame
Braden
BradenOP•3mo ago
but I could do something like:
local function system(world)
local q = world:query():cached()
return function()
for _ in q do end
end
end
local function system(world)
local q = world:query():cached()
return function()
for _ in q do end
end
end
and be fine?
PepeElToro41
PepeElToro41•3mo ago
ya actually I use the old scheduler of the demo this is the new one
Braden
BradenOP•3mo ago
I'll look at switching over then, at this rate my game is gonna die due to the server lag 🤣
PepeElToro41
PepeElToro41•3mo ago
yeah, the old one didnt pass the world in the scheduler there is always a single world in my game so I dont need that
Braden
BradenOP•3mo ago
that is crucial to me but I would assume it would be easy to do additionally I need a maid passed in as well to cleanup startup functions observers, etc
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
understood! I will not how many times should cache be created anyways?
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
so if an entitiy's components change the cached query will still iterate the changes
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
got it i appreciate it
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
in OSS?
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
no, this was horrible and its what I did to begin with. made it really hard to scale. I just use a list of behavior tasks and then check a test to see if its success,failed, or running Here is my behavior runner that basically just queries over BehaviorStep which stores the current step and the sequence. Think sequence = "Going to Eat" and a step would be "Walking to Cafeteria Table"
for (const [entityID, behavior] of query) {
const sequence = BehaviorSequences[behavior.sequence];
if (!sequence) {
warn(`COULD NOT FIND SEQUENCE: ${behavior.sequence} DOES IT EXIST?`);
world.remove(entityID, sharedComponents.BehaviorStep);
continue;
}

if (behavior.name === "" || behavior.name === undefined) {
behavior.name = sequence.initial;
// world.set(entityID, sharedComponents.BehaviorStep, { name: sequence.initial, sequence: behavior.sequence });
}
if (world.has(entityID, sharedComponents.MarkForDelete)) {
behavior.name = "Cleanup";
// world.set(entityID, sharedComponents.BehaviorStep, { name: "Cleanup", sequence: behavior.sequence });
}
if (behavior.name === "Cleanup") {
world.remove(entityID, sharedComponents.NPCStates.Walking);
world.add(entityID, sharedComponents.NPCStates.Idle as Entity<undefined>);
}

const step = sequence.steps[behavior.name];
if (!step) {
warn(`COULD NOT FIND STEP: ${behavior.name} DOES IT EXIST?`);
world.remove(entityID, sharedComponents.BehaviorStep);
world.remove(entityID, sharedComponents.CurrentTask);
continue;
}

// patchNPC(sharedWorld, entityID, { $currentTask: step.taskName ?? step.name });

if (
!world.has(entityID, sharedComponents.CurrentTask) ||
world.get(entityID, sharedComponents.CurrentTask) !== step.taskName
) {
if (step.taskName !== undefined) {
world.set(entityID, sharedComponents.CurrentTask, step.taskName);
} else {
world.remove(entityID, sharedComponents.CurrentTask);
}
}

const status = step.execute(world, entityID);

if (status === StepStatus.Success && step.next) {
behavior.name = step.next;
// world.set(entityID, sharedComponents.BehaviorStep, { name: step.name, sequence: behavior.sequence });
// sharedWorld.set(entityID, sharedComponents.BehaviorStep, { name: step.next, sequence: behavior.sequence });
} else if (status === StepStatus.Failed && step.fallback) {
behavior.name = step.fallback;
// world.set(entityID, sharedComponents.BehaviorStep, { name: step.fallback, sequence: behavior.sequence });
// sharedWorld.set(entityID, sharedComponents.BehaviorStep, { name: step.fallback, sequence: behavior.sequence });
}
if (step.next === undefined) {
// we can assume behavior is complete
world.remove(entityID, sharedComponents.BehaviorStep);
world.remove(entityID, sharedComponents.CurrentTask);
}
}
for (const [entityID, behavior] of query) {
const sequence = BehaviorSequences[behavior.sequence];
if (!sequence) {
warn(`COULD NOT FIND SEQUENCE: ${behavior.sequence} DOES IT EXIST?`);
world.remove(entityID, sharedComponents.BehaviorStep);
continue;
}

if (behavior.name === "" || behavior.name === undefined) {
behavior.name = sequence.initial;
// world.set(entityID, sharedComponents.BehaviorStep, { name: sequence.initial, sequence: behavior.sequence });
}
if (world.has(entityID, sharedComponents.MarkForDelete)) {
behavior.name = "Cleanup";
// world.set(entityID, sharedComponents.BehaviorStep, { name: "Cleanup", sequence: behavior.sequence });
}
if (behavior.name === "Cleanup") {
world.remove(entityID, sharedComponents.NPCStates.Walking);
world.add(entityID, sharedComponents.NPCStates.Idle as Entity<undefined>);
}

const step = sequence.steps[behavior.name];
if (!step) {
warn(`COULD NOT FIND STEP: ${behavior.name} DOES IT EXIST?`);
world.remove(entityID, sharedComponents.BehaviorStep);
world.remove(entityID, sharedComponents.CurrentTask);
continue;
}

// patchNPC(sharedWorld, entityID, { $currentTask: step.taskName ?? step.name });

if (
!world.has(entityID, sharedComponents.CurrentTask) ||
world.get(entityID, sharedComponents.CurrentTask) !== step.taskName
) {
if (step.taskName !== undefined) {
world.set(entityID, sharedComponents.CurrentTask, step.taskName);
} else {
world.remove(entityID, sharedComponents.CurrentTask);
}
}

const status = step.execute(world, entityID);

if (status === StepStatus.Success && step.next) {
behavior.name = step.next;
// world.set(entityID, sharedComponents.BehaviorStep, { name: step.name, sequence: behavior.sequence });
// sharedWorld.set(entityID, sharedComponents.BehaviorStep, { name: step.next, sequence: behavior.sequence });
} else if (status === StepStatus.Failed && step.fallback) {
behavior.name = step.fallback;
// world.set(entityID, sharedComponents.BehaviorStep, { name: step.fallback, sequence: behavior.sequence });
// sharedWorld.set(entityID, sharedComponents.BehaviorStep, { name: step.fallback, sequence: behavior.sequence });
}
if (step.next === undefined) {
// we can assume behavior is complete
world.remove(entityID, sharedComponents.BehaviorStep);
world.remove(entityID, sharedComponents.CurrentTask);
}
}
The main thing is I return StepStatus.Running if the task needs more time (yields) or is waiting for pathfinding response or waiting for the NPC to actually move to the area
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
Its just a table of functions that return a step status, its very long and has many dependencies on other functions. I pass in the NPC entity ID and world to make changes
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View
Braden
BradenOP•3mo ago
export const DoResearchSteps: Record<string, BehaviorStep> = {
LookingForDesk: {
name: "LookingForDesk",
taskName: "Looking for Desk",
execute: (world: World, entityID: Entity<unknown>) => {
return StepStatus.Success;
},
next: "MovingToDesk",
fallback: "Cleanup",
}
}
export const DoResearchSteps: Record<string, BehaviorStep> = {
LookingForDesk: {
name: "LookingForDesk",
taskName: "Looking for Desk",
execute: (world: World, entityID: Entity<unknown>) => {
return StepStatus.Success;
},
next: "MovingToDesk",
fallback: "Cleanup",
}
}
Unknown User
Unknown User•3mo ago
Message Not Public
Sign In & Join Server To View

Did you find this page helpful?