@croquet/worldcore-behavior
Advanced tools
Comparing version 1.2.0 to 1.3.0
@@ -7,2 +7,10 @@ # Changelog | ||
## [1.3.0] - 2022-6-16 | ||
## Changed | ||
- Behaviors are Actors | ||
- BehaviorPawn provides a view-side interface to individual behaviors in the tree | ||
- Behaviors have a code snippet to allow live editing | ||
- PM_Behavioral can be added to pawns to access the object's behavior tree | ||
## [1.0.0] - 2021-10-20 | ||
@@ -13,1 +21,4 @@ ### Added | ||
## Pending | ||
{ | ||
"name": "@croquet/worldcore-behavior", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "Behavior Tree Component for Croquet Worldcore", | ||
@@ -27,3 +27,3 @@ "keywords": [ | ||
"dependencies": { | ||
"@croquet/worldcore-kernel": "^1.2.0" | ||
"@croquet/worldcore-kernel": "^1.3.0" | ||
}, | ||
@@ -33,3 +33,3 @@ "publishConfig": { | ||
}, | ||
"gitHead": "7633b5da452e79cee2be15721d744e4cd9f9924c" | ||
"gitHead": "d5e33664baaaa1a3a153e0aa3721ad3a17c25dc3" | ||
} |
@@ -1,5 +0,5 @@ | ||
import { RegisterMixin, WorldcoreModel, Shuffle } from "@croquet/worldcore-kernel"; | ||
// import { } from "@croquet/worldcore-kernel"; | ||
// import { Shuffle } from "./Utilities"; | ||
import { RegisterMixin, WorldcoreModel, Shuffle, Actor, Pawn, GetPawn } from "@croquet/worldcore-kernel"; | ||
import * as Worldcore from "@croquet/worldcore-kernel"; | ||
//------------------------------------------------------------------------------------------ | ||
@@ -9,6 +9,13 @@ //-- Behavioral ---------------------------------------------------------------------------- | ||
// StartBehavior sets the actor's current root behavior. | ||
// Mixin to allow actors to use behavior trees. | ||
export const AM_Behavioral = superclass => class extends superclass { | ||
// StartBehavior sets the actor's current root behavior. | ||
export const AM_Behavioral = superclass => class extends superclass { | ||
init(...args) { | ||
super.init(...args); | ||
this.listen("setBehaviorCode", this.setBehaviorCode); | ||
} | ||
destroy() { | ||
@@ -22,17 +29,58 @@ super.destroy(); | ||
options.actor = this; | ||
this.behavior = behavior.create(options); | ||
const target = behavior.create(options); | ||
this.behavior = target; | ||
} | ||
setBehaviorCode(code) { | ||
this.behavior.set({code: code}); | ||
} | ||
} | ||
RegisterMixin(AM_Behavioral); | ||
//Mixin to allow view-side access of the behavior tree. | ||
export const PM_Behavioral = superclass => class extends superclass { | ||
get behavior() { if(this.actor.behavior) return GetPawn(this.actor.behavior.id) } | ||
} | ||
//------------------------------------------------------------------------------------------ | ||
//-- BehaviorHandler ----------------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
//This is the default handler for the behavior proxies. It traps all method calls | ||
// and uses the code snippet instead. If you want to call the base methods from the code snippet, | ||
// you can with "super.onStart()" (for example) | ||
class BehaviorHandler { | ||
get(target, prop, receiver) { | ||
if(this[prop]) return this[prop]; | ||
if(prop === "base") return target; | ||
return Reflect.get(...arguments); | ||
} | ||
onStart() { | ||
this.base.onStart(); // Fall thru to the behavior. | ||
} | ||
do(delta) { | ||
this.base.do(delta); // Fall thru to the behavior. | ||
} | ||
} | ||
//------------------------------------------------------------------------------------------ | ||
//-- Behavior ------------------------------------------------------------------------------ | ||
//------------------------------------------------------------------------------------------ | ||
export class Behavior extends WorldcoreModel { | ||
export class Behavior extends Actor { | ||
get pawn() { return BehaviorPawn; } | ||
init(options) { | ||
super.init(); | ||
this.set(options); | ||
if (this.parent) this.parent.addChild(this); | ||
super.init(options); | ||
this.listen("_code", this.clearProxy); // Flush the proxy if the code changes. | ||
@@ -43,18 +91,21 @@ if (this.tickRate) { | ||
} | ||
} | ||
destroy() { | ||
super.destroy(); | ||
if (this.parent) this.parent.removeChild(this); | ||
this.doomed = true; | ||
this.proxy.onStart(); | ||
} | ||
set(options) { | ||
for (const option in options) { | ||
this["_" + option] = options[option]; | ||
clearProxy() { this.$proxy = null } | ||
get proxy() { | ||
if (!this.$proxy) { | ||
const factoryCode = `return class extends superclass { ${this.code} }`; | ||
const factory = new Function('superclass', 'WC', factoryCode); | ||
this.$proxy = new Proxy(this, factory(BehaviorHandler, Worldcore).prototype); | ||
} | ||
return this.$proxy; | ||
} | ||
get code() { return this._code} | ||
get actor() { return this._actor} | ||
get parent() { return this._parent} | ||
get tickRate() { return this._tickRate || 100} | ||
@@ -64,10 +115,14 @@ | ||
if (this.doomed) return; | ||
this.do(delta); | ||
this.proxy.do(delta); | ||
if (!this.doomed) this.future(this.tickRate).tick(this.tickRate); | ||
} | ||
do(delta) {} | ||
startChild(behavior, options = {}) { | ||
options.actor = this.actor; | ||
options.parent = this; | ||
behavior.create(options); | ||
} | ||
succeed(data) { | ||
if (this.parent) this.parent.reportSuccess(this, data); | ||
if (this.parent) this.parent.proxy.onSucceed(this, data); | ||
this.destroy(); | ||
@@ -77,9 +132,24 @@ } | ||
fail(data) { | ||
if (this.parent) this.parent.reportFailure(this, data); | ||
if (this.parent) this.parent.proxy.onFail(this, data); | ||
this.destroy(); | ||
} | ||
onStart() {} | ||
do(delta) {} | ||
onSucceed(child, data) { this.succeed(data) }; | ||
onFail(child, data) { this.fail(data) }; | ||
} | ||
Behavior.register('Behavior'); | ||
// Behaviors don't have to have pawns. BehaviorPawn just provides a view-side interface for changing the code snippet. | ||
// The BehaviorPawns replicate the structure of the behavior tree so you can use this to inspect the current state of the tree. | ||
export class BehaviorPawn extends Pawn { | ||
get code() { return this.actor.code } | ||
set code(code) { this.set( {code: code}) } | ||
} | ||
//------------------------------------------------------------------------------------------ | ||
@@ -89,3 +159,3 @@ //-- CompositeBehavior --------------------------------------------------------------------- | ||
// Behaviors with children. They don't tick themselves, but can respond to reported success or | ||
// Behaviors with multiple child behaviors. They don't tick themselves, but respond to reported success or | ||
// failure by their children who are ticking. | ||
@@ -97,26 +167,24 @@ | ||
get behaviors() {return []} | ||
get isParallel() { return this._parallel } | ||
get isShuffle() { return this._shuffle } | ||
destroy() { | ||
super.destroy(); | ||
new Set(this.children).forEach(child => child.destroy()); | ||
onStart() { | ||
this.n = 0; | ||
this.pending = this.behaviors.length; | ||
if (this.isShuffle) this.deck = Shuffle(this.behaviors.length); | ||
this.isParallel ? this.startAll() : this.startNext(); | ||
} | ||
startChild(behavior, options = {}) { | ||
options.actor = this.actor; | ||
options.parent = this; | ||
behavior.create(options); | ||
startAll() { | ||
for (let i = 0; i < this.behaviors.length; i++ ) { | ||
if (this.doomed) return; | ||
this.isShuffle ? this.startChild(this.behaviors[this.deck[i]]) : this.startChild(this.behaviors[i]); | ||
} | ||
} | ||
addChild(child) { | ||
if (!this.children) this.children = new Set(); | ||
this.children.add(child); | ||
startNext() { | ||
if (this.isParallel) return; | ||
this.isShuffle ? this.startChild(this.behaviors[this.deck[this.n++]]) : this.startChild(this.behaviors[this.n++]); | ||
} | ||
removeChild(child) { | ||
if (this.children) this.children.delete(child); | ||
} | ||
reportSuccess(child, data) {}; | ||
reportFailure(child, data) { this.fail(data)}; | ||
} | ||
@@ -130,23 +198,11 @@ CompositeBehavior.register('CompositeBehavior'); | ||
// Executes a sequence of behaviors in order. Fails if any of them fails. Nothing is | ||
// excecuted after the first fail. | ||
// executed after the first fail. Succeeds if all of them succeed. | ||
// The equivalent of a logical AND | ||
export class SequenceBehavior extends CompositeBehavior { | ||
init(options) { | ||
super.init(options); | ||
this.n = 0; | ||
this.next(); | ||
} | ||
onSucceed() { --this.pending ? this.startNext() : this.succeed(); } | ||
onFail() { this.fail(); } | ||
next() { | ||
if (this.n < this.behaviors.length) { | ||
this.startChild(this.behaviors[this.n++]); | ||
} else { | ||
this.succeed(); | ||
} | ||
} | ||
reportSuccess() { this.next(); } | ||
// reportFailure() { this.fail(); } | ||
} | ||
@@ -156,60 +212,2 @@ SequenceBehavior.register('SequenceBehavior'); | ||
//------------------------------------------------------------------------------------------ | ||
//-- RandomSequenceBehavior ---------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
// Executes all children in random order. Fails if any of them fails. Nothing is excecuted after | ||
// the first fail. | ||
export class RandomSequenceBehavior extends CompositeBehavior { | ||
init(options) { | ||
super.init(options); | ||
this.n = 0; | ||
this.order = Shuffle(this.behaviors.length); | ||
this.next(); | ||
} | ||
next() { | ||
if (this.n < this.behaviors.length) { | ||
const pick = this.order[this.n++]; | ||
this.startChild(this.behaviors[pick]); | ||
} else { | ||
this.succeed(); | ||
} | ||
} | ||
reportSuccess() { this.next(); } | ||
// reportFailure() { this.fail(); } | ||
} | ||
RandomSequenceBehavior.register('RandomSequenceBehavior'); | ||
//------------------------------------------------------------------------------------------ | ||
//-- ParallelSequenceBehavior -------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
// Executes all childred simultaenously. Succeeds if all children succeed. | ||
// Fails if one child fails. Aborts other children after first failure. | ||
export class ParallelSequenceBehavior extends CompositeBehavior { | ||
init(options) { | ||
super.init(options); | ||
this.behaviors.forEach (behavior => { | ||
if (this.doomed) return; | ||
this.startChild(behavior) | ||
}) | ||
} | ||
reportSuccess() { | ||
if (this.children.size > 0) return; | ||
this.succeed(); | ||
} | ||
// reportFailure() { this.fail(); } | ||
} | ||
ParallelSequenceBehavior.register('ParallelSequenceBehavior'); | ||
//------------------------------------------------------------------------------------------ | ||
//-- SelectorBehavior ---------------------------------------------------------------------- | ||
@@ -219,23 +217,12 @@ //------------------------------------------------------------------------------------------ | ||
// Executes behaviors in order. Succeeds if any of them succeeds. Nothing is executed after | ||
// the first success. | ||
// the first success. Fails if all of them fail. | ||
// The equivalent of a logical OR | ||
export class SelectorBehavior extends CompositeBehavior { | ||
init(options) { | ||
super.init(options); | ||
this.n = 0; | ||
this.next(); | ||
} | ||
onSucceed() { this.succeed(); } | ||
onFail() { --this.pending ? this.startNext() : this.fail(); } | ||
next() { | ||
if (this.n < this.behaviors.length) { | ||
this.startChild(this.behaviors[this.n++]); | ||
} else { | ||
this.fail(); | ||
} | ||
} | ||
reportSuccess() { this.succeed(); } | ||
reportFailure() { this.next(); } | ||
} | ||
@@ -245,60 +232,2 @@ SelectorBehavior.register('SelectorBehavior'); | ||
//------------------------------------------------------------------------------------------ | ||
//-- RandomSelectorBehavior ---------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
// Executes behaviors in random order. Succeeds if any of them succeeds. Nothing is executed after | ||
// the first success. | ||
export class RandomSelectorBehavior extends CompositeBehavior { | ||
init(options) { | ||
super.init(options); | ||
this.order = Shuffle(this.behaviors.length); | ||
this.n = 0; | ||
this.next(); | ||
} | ||
next() { | ||
if (this.n < this.behaviors.length) { | ||
const pick = this.order[this.n++]; | ||
this.startChild(this.behaviors[pick]); | ||
} else { | ||
this.fail(); | ||
} | ||
} | ||
reportSuccess() { this.succeed(); } | ||
reportFailure() { this.next(); } | ||
} | ||
RandomSelectorBehavior.register('RandomSelectorBehavior'); | ||
//------------------------------------------------------------------------------------------ | ||
//-- ParallelSelectorBehavior -------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
// Executes all childred simultaenously. Fails if all children fail. | ||
// Succeeds if one child succeeds. Aborts other children after first success. | ||
export class ParallelSelectorBehavior extends CompositeBehavior { | ||
init(options) { | ||
super.init(options); | ||
this.behaviors.forEach (behavior => { | ||
if (this.doomed) return; | ||
this.startChild(behavior) | ||
}) | ||
} | ||
reportSuccess() { this.succeed(); } | ||
reportFailure() { | ||
if (this.children.size > 0) return; | ||
this.fail(); | ||
} | ||
} | ||
ParallelSelectorBehavior.register('ParallelSelectorBehavior'); | ||
//------------------------------------------------------------------------------------------ | ||
//-- DecoratorBehavior --------------------------------------------------------------------- | ||
@@ -312,31 +241,6 @@ //------------------------------------------------------------------------------------------ | ||
get tickRate() { return 0 } | ||
get behavior() { return null} | ||
get behavior() { return null } | ||
init(options) { | ||
super.init(options); | ||
this.startChild(); | ||
} | ||
onStart() { this.startChild(this.behavior); } | ||
destroy() { | ||
super.destroy(); | ||
if (this.child) this.child.destroy(); | ||
} | ||
startChild(options = {}) { | ||
options.actor = this.actor; | ||
options.parent = this; | ||
this.behavior.create(options); | ||
} | ||
addChild(child) { | ||
this.child = child; | ||
} | ||
removeChild(child) { | ||
if (this.child === child) this.child = null; | ||
} | ||
reportSuccess(child, data) {this.succeed(data)}; | ||
reportFailure(child, data) {this.fail(data)}; | ||
} | ||
@@ -349,8 +253,8 @@ DecoratorBehavior.register('DecoratorBehavior'); | ||
// Holds a single child behavior. Inverts its completion status when it fnishes. | ||
// Inverts completion status when child finishes. | ||
export class InvertBehavior extends DecoratorBehavior { | ||
reportSuccess(child, data) {this.fail(data)}; | ||
reportFailure(child, data) {this.succeed(data)}; | ||
onSucceed(child, data) {this.fail(data)}; | ||
onFail(child, data) {this.succeed(data)}; | ||
@@ -364,8 +268,8 @@ } | ||
// Holds a single child behavior. Always returns success when it finishes. | ||
// Always returns success when the child finishes. | ||
export class SucceedBehavior extends DecoratorBehavior { | ||
reportSuccess(child, data) {this.succeed(data)}; | ||
reportFailure(child, data) {this.succeed(data)}; | ||
onSucceed(child, data) { this.succeed(data) }; | ||
onFail(child, data) { this.succeed(data) }; | ||
@@ -379,8 +283,8 @@ } | ||
// Holds a single child behavior. Always returns failure when it finishes. | ||
// Always returns fail when the child finishes. | ||
export class FailBehavior extends DecoratorBehavior { | ||
reportSuccess(child, data) {this.fail(data)}; | ||
reportFailure(child, data) {this.fail(data)}; | ||
onSucceed(child, data) {this.fail(data)}; | ||
onFail(child, data) {this.fail(data)}; | ||
@@ -391,68 +295,39 @@ } | ||
//------------------------------------------------------------------------------------------ | ||
//-- LoopBehavior -------------------------------------------------------------------------- | ||
//-- DelayBehavior ------------------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
// Holds a single child behavior. Will repeatedly execute it until count is reached, as long | ||
// as it succeeds. If it fails, the loop returns failure. | ||
// | ||
// If the count is set to 0, it executes indefinitely. | ||
// | ||
// Note that if the child completes instantly and the count is high, you will probably overrun | ||
// the call stack. | ||
// Succeeds when delay time is reached. | ||
export class LoopBehavior extends DecoratorBehavior { | ||
export class DelayBehavior extends Behavior { | ||
get count() {return this._count || 0} | ||
get tickRate() { return 0 } | ||
get delay() { return this._delay || 1000}; | ||
init(options) { | ||
super.init(options); | ||
if (this.count) this.n = 0; | ||
} | ||
onStart() { this.future(this.delay).succeed(); } | ||
reportSuccess(child, data) { | ||
if (this.count) { | ||
this.n++; | ||
if (this.n === this.count) { | ||
this.succeed(); | ||
return; | ||
} | ||
} | ||
this.startChild(); | ||
} | ||
} | ||
LoopBehavior.register('LoopBehavior'); | ||
DelayBehavior.register("DelayBehavior"); | ||
//------------------------------------------------------------------------------------------ | ||
//-- DestroyBehavior ----------------------------------------------------------------------- | ||
//-- LoopBehavior -------------------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
// Destroys the actor | ||
// Repeatedly executes a child behavior until count is reached, as long | ||
// as it succeeds. If it fails, the loop returns failure. If the count is set to 0, it executes indefinitely. | ||
export class DestroyBehavior extends Behavior { | ||
export class LoopBehavior extends DecoratorBehavior { | ||
init(options) { | ||
super.init(options); | ||
this.actor.destroy(); | ||
get count() {return this._count || 0} | ||
onStart() { | ||
this.n = 0; | ||
this.startChild(this.behavior); | ||
} | ||
} | ||
DestroyBehavior.register("DestroyBehavior"); | ||
//------------------------------------------------------------------------------------------ | ||
//-- DelayBehavior ------------------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
onSucceed() { ++this.n === this.count ? this.succeed() : this.startChild(this.behavior); } | ||
onFail() { this.fail(); } | ||
// Succeeds when delay time is reached. | ||
} | ||
LoopBehavior.register('LoopBehavior'); | ||
export class DelayBehavior extends Behavior { | ||
get tickRate() { return 0 } | ||
get delay() { return this._delay || 1000}; | ||
init(options) { | ||
super.init(options); | ||
this.future(this.delay).succeed(); | ||
} | ||
} | ||
DelayBehavior.register("DelayBehavior"); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
13452
219
2