1+ // Import Third-party Dependencies
2+ import pm from "picomatch" ;
3+
14// Import Internal Dependencies
25import { Actor } from "./Actor.js" ;
36
@@ -15,10 +18,11 @@ export class ActorTree extends EventTarget {
1518 #addCallback?: ( actor : Actor ) => void ;
1619 #removeCallback?: ( actor : Actor ) => void ;
1720
18- root : Actor [ ] = [ ] ;
19- actorsToBeDestroyed : Actor [ ] = [ ] ;
21+ children : Actor [ ] = [ ] ;
2022
21- constructor ( options : ActorTreeOptions = { } ) {
23+ constructor (
24+ options : ActorTreeOptions = { }
25+ ) {
2226 super ( ) ;
2327 this . #addCallback = options . addCallback ;
2428 this . #removeCallback = options . removeCallback ;
@@ -27,21 +31,119 @@ export class ActorTree extends EventTarget {
2731 add (
2832 actor : Actor
2933 ) : void {
30- this . root . push ( actor ) ;
34+ this . children . push ( actor ) ;
3135 this . #addCallback?.( actor ) ;
3236 }
3337
3438 remove ( actor : Actor ) : void {
35- const index = this . root . indexOf ( actor ) ;
39+ const index = this . children . indexOf ( actor ) ;
3640 if ( index !== - 1 ) {
37- this . root . splice ( index , 1 ) ;
41+ this . children . splice ( index , 1 ) ;
3842 this . #removeCallback?.( actor ) ;
3943 }
4044 }
4145
46+ * getActors (
47+ pattern : string
48+ ) : IterableIterator < Actor > {
49+ if ( pattern . includes ( "/" ) ) {
50+ yield * this . #getActorsByPatternPath( pattern ) ;
51+
52+ return ;
53+ }
54+
55+ const isPatternMatching = pm ( pattern ) ;
56+
57+ for ( const { actor } of this . walk ( ) ) {
58+ if ( isPatternMatching ( actor . name ) && ! actor . pendingForDestruction ) {
59+ yield actor ;
60+ }
61+ }
62+ }
63+
64+ * #getActorsByPatternPath(
65+ pattern : string
66+ ) : IterableIterator < Actor > {
67+ const parts = pattern . split ( "/" ) . filter ( ( part ) => part !== "" ) ;
68+
69+ for ( const rootActor of this . children ) {
70+ if ( ! rootActor . pendingForDestruction ) {
71+ yield * this . #matchActorPath( rootActor , parts , 0 ) ;
72+ }
73+ }
74+ }
75+
76+ * #matchActorPath(
77+ actor : Actor ,
78+ patternParts : string [ ] ,
79+ patternIndex : number
80+ ) : IterableIterator < Actor > {
81+ if ( patternIndex >= patternParts . length ) {
82+ return ;
83+ }
84+
85+ const currentPattern = patternParts [ patternIndex ] ;
86+ const isLastPattern = patternIndex === patternParts . length - 1 ;
87+
88+ // eslint-disable-next-line func-style
89+ const matchSinglePattern = ( name : string , pattern : string ) => pm ( pattern ) ( name ) ;
90+
91+ if ( currentPattern === "**" ) {
92+ if ( isLastPattern ) {
93+ for ( const { actor : descendant } of this . #walkDepthFirstGenerator( actor ) ) {
94+ if ( ! descendant . pendingForDestruction ) {
95+ yield descendant ;
96+ }
97+ }
98+
99+ return ;
100+ }
101+
102+ const nextPattern = patternParts [ patternIndex + 1 ] ;
103+ for ( const { actor : descendant } of this . #walkDepthFirstGenerator( actor ) ) {
104+ if ( descendant . pendingForDestruction ) {
105+ continue ;
106+ }
107+
108+ if ( matchSinglePattern ( descendant . name , nextPattern ) ) {
109+ if ( patternIndex + 1 === patternParts . length - 1 ) {
110+ yield descendant ;
111+ }
112+ else {
113+ yield * this . #matchActorPath( descendant , patternParts , patternIndex + 2 ) ;
114+ }
115+ }
116+ }
117+
118+ return ;
119+ }
120+
121+ if ( matchSinglePattern ( actor . name , currentPattern ) ) {
122+ if ( isLastPattern ) {
123+ if ( ! actor . pendingForDestruction ) {
124+ yield actor ;
125+ }
126+ }
127+ else {
128+ for ( const child of actor . children ) {
129+ yield * this . #matchActorPath( child , patternParts , patternIndex + 1 ) ;
130+ }
131+ }
132+ }
133+ }
134+
135+ /**
136+ * @example
137+ * const player = actor.children.getActor("player");
138+ * const playerPhysicsBox = actor.children.getActor("player/physics_box");
139+ */
42140 getActor (
43141 name : string
44142 ) : Actor | null {
143+ if ( name . includes ( "/" ) ) {
144+ return this . #getActorByPath( name ) ;
145+ }
146+
45147 for ( const { actor } of this . walk ( ) ) {
46148 if ( actor . name === name && ! actor . pendingForDestruction ) {
47149 return actor ;
@@ -51,8 +153,31 @@ export class ActorTree extends EventTarget {
51153 return null ;
52154 }
53155
156+ #getActorByPath(
157+ path : string
158+ ) : Actor | null {
159+ const parts = path . split ( "/" ) . filter ( ( part ) => part !== "" ) ;
160+ const parentNode = this . getActor ( parts [ 0 ] ) ;
161+ if ( ! parentNode ) {
162+ return null ;
163+ }
164+
165+ let currentNode : Actor | null = parentNode ;
166+ for ( let i = 1 ; i < parts . length ; i ++ ) {
167+ if ( ! currentNode ) {
168+ break ;
169+ }
170+
171+ currentNode = [ ...currentNode . children ] . find (
172+ ( child ) => child . name === parts [ i ]
173+ ) ?? null ;
174+ }
175+
176+ return currentNode ;
177+ }
178+
54179 * getRootActors ( ) : IterableIterator < Actor > {
55- for ( const rootActor of this . root ) {
180+ for ( const rootActor of this . children ) {
56181 if ( ! rootActor . pendingForDestruction ) {
57182 yield rootActor ;
58183 }
@@ -69,7 +194,6 @@ export class ActorTree extends EventTarget {
69194 actor : Actor
70195 ) {
71196 if ( ! actor . pendingForDestruction ) {
72- this . actorsToBeDestroyed . push ( actor ) ;
73197 actor . markDestructionPending ( ) ;
74198 }
75199 }
@@ -94,7 +218,7 @@ export class ActorTree extends EventTarget {
94218 }
95219
96220 * walk ( ) : IterableIterator < ActorTreeNode > {
97- for ( const child of this . root ) {
221+ for ( const child of this . children ) {
98222 yield * this . #walkDepthFirstGenerator( child , undefined ) ;
99223 }
100224 }
@@ -106,4 +230,12 @@ export class ActorTree extends EventTarget {
106230 yield * this . #walkDepthFirstGenerator( child , rootNode ) ;
107231 }
108232 }
233+
234+ * [ Symbol . iterator ] ( ) : IterableIterator < Actor > {
235+ for ( const actor of this . children ) {
236+ if ( ! actor . pendingForDestruction ) {
237+ yield actor ;
238+ }
239+ }
240+ }
109241}
0 commit comments