From 9c4694026ae1189d02ce02baae837c2deac99e07 Mon Sep 17 00:00:00 2001 From: Leslie Leigh Date: Thu, 16 Sep 2021 18:59:03 +0800 Subject: [PATCH] Optimize variable bind --- .../__tmp__/graph-from-description.ts | 22 +-- cocos/core/animation/newgen-anim/condition.ts | 117 ++++++++----- .../core/animation/newgen-anim/graph-eval.ts | 77 ++++---- cocos/core/animation/newgen-anim/index.ts | 2 +- .../core/animation/newgen-anim/parametric.ts | 164 +++++++----------- .../animation/newgen-anim/pose-blend-1d.ts | 37 ++-- .../animation/newgen-anim/pose-blend-2d.ts | 64 +++---- cocos/core/animation/newgen-anim/pose-node.ts | 16 +- cocos/core/animation/newgen-anim/pose.ts | 6 +- tests/animation/newgenanim.test.ts | 55 +++--- 10 files changed, 234 insertions(+), 326 deletions(-) diff --git a/cocos/core/animation/newgen-anim/__tmp__/graph-from-description.ts b/cocos/core/animation/newgen-anim/__tmp__/graph-from-description.ts index 70bacffb17d..78f8407426d 100644 --- a/cocos/core/animation/newgen-anim/__tmp__/graph-from-description.ts +++ b/cocos/core/animation/newgen-anim/__tmp__/graph-from-description.ts @@ -12,7 +12,7 @@ import { PoseBlend1D } from '../pose-blend-1d'; import { PoseBlend2D } from '../pose-blend-2d'; import { GraphNode, PoseSubgraph, PoseTransition, VariableType } from '../pose-graph'; import { Pose } from '../pose'; -import { BindingHost } from '../parametric'; +import { Bindable } from '../parametric'; import { Value } from '../variable'; import { PoseNode } from '../pose-node'; import { BinaryCondition, TriggerCondition, UnaryCondition } from '../condition'; @@ -84,14 +84,14 @@ function createTransition (graph: PoseSubgraph, from: GraphNode, to: GraphNode, case 'unary': { const condition = new UnaryCondition(); condition.operator = UnaryCondition.Operator[conditionDesc.type]; - condition.operand = createParametric(conditionDesc.operand, condition, 'operand'); + createParametric(conditionDesc.operand, condition.operand); return condition; } case 'binary': { const condition = new BinaryCondition(); condition.operator = BinaryCondition.Operator[conditionDesc.type]; - condition.lhs = createParametric(conditionDesc.lhs, condition, 'lhs'); - condition.rhs = createParametric(conditionDesc.rhs, condition, 'rhs'); + createParametric(conditionDesc.lhs, condition.lhs); + createParametric(conditionDesc.rhs, condition.rhs); return condition; } case 'trigger': { @@ -133,7 +133,7 @@ function createMotion (motionDesc: MotionDescription): Pose { createMotion(childMotionDesc), thresholds[iMotion], ] as [Pose, number]); - motion.param = createParametric(motionDesc.blender.value, motion, 'param'); + createParametric(motionDesc.blender.value, motion.param); return motion; } else { const algorithm = PoseBlend2D.Algorithm[motionDesc.blender.algorithm]; @@ -144,18 +144,18 @@ function createMotion (motionDesc: MotionDescription): Pose { createMotion(childMotionDesc), new Vec2(thresholds[iMotion].x, thresholds[iMotion].y), ] as [Pose, Vec2]); - motion.paramX = createParametric(motionDesc.blender.values[0], motion, 'paramX'); - motion.paramY = createParametric(motionDesc.blender.values[1], motion, 'paramY'); + createParametric(motionDesc.blender.values[0], motion.paramX); + createParametric(motionDesc.blender.values[1], motion.paramY); return motion; } } -function createParametric (paramDesc: ParametricDescription, host: BindingHost, bindingPointId: string) { +function createParametric (paramDesc: ParametricDescription, bindable: Bindable) { if (typeof paramDesc === 'object') { - host.bindProperty(bindingPointId, paramDesc.name); - return paramDesc.value; + bindable.variable = paramDesc.name; + bindable.value = paramDesc.value; } else { - return paramDesc; + bindable.value = paramDesc; } } diff --git a/cocos/core/animation/newgen-anim/condition.ts b/cocos/core/animation/newgen-anim/condition.ts index 93d939e68c1..4af6043bf34 100644 --- a/cocos/core/animation/newgen-anim/condition.ts +++ b/cocos/core/animation/newgen-anim/condition.ts @@ -1,12 +1,15 @@ +import { VariableNotDefinedError, VariableType } from '.'; import { ccclass, serializable } from '../../data/decorators'; import { CLASS_NAME_PREFIX_ANIM } from '../define'; import { createEval } from './create-eval'; import { VariableTypeMismatchedError } from './errors'; -import { BindingHost, parametric } from './parametric'; +import { BindableBoolean, BindableNumber, BindContext, bindOr } from './parametric'; import type { Value } from './variable'; -export interface Condition extends BindingHost { - [createEval] (context: { getParam(host: BindingHost, name: string): unknown; }): ConditionEval; +export type ConditionEvalContext = BindContext; + +export interface Condition { + [createEval] (context: BindContext): ConditionEval; } export interface ConditionEval { @@ -26,31 +29,37 @@ enum BinaryOperator { } @ccclass(`${CLASS_NAME_PREFIX_ANIM}BinaryCondition`) -export class BinaryCondition extends BindingHost implements Condition { +export class BinaryCondition implements Condition { public static readonly Operator = BinaryOperator; @serializable public operator!: BinaryOperator; @serializable - @parametric({ - notify: (value: Value, conditionEval: BinaryConditionEval) => conditionEval.setLhs(value), - }) - public lhs!: Value; + public lhs: BindableNumber = new BindableNumber(); @serializable - @parametric({ - notify: (value: Value, conditionEval: BinaryConditionEval) => conditionEval.setRhs(value), - }) - public rhs: Value | undefined; - - public [createEval] (context: { getParam(host: BindingHost, name: string): unknown; }) { - const { operator } = this; - const lhs = context.getParam(this, 'lhs') ?? this.lhs; - const rhs = context.getParam(this, 'rhs') ?? this.rhs; - validateConditionParamNumber(lhs, 'lhs'); - validateConditionParamNumber(rhs, 'rhs'); - return new BinaryConditionEval(operator, lhs, rhs as Value | undefined); + public rhs: BindableNumber = new BindableNumber(); + + public [createEval] (context: BindContext) { + const { operator, lhs, rhs } = this; + const evaluation = new BinaryConditionEval(operator, 0.0, 0.0); + const lhsValue = bindOr( + context, + lhs, + VariableType.NUMBER, + evaluation.setLhs, + evaluation, + ); + const rhsValue = bindOr( + context, + rhs, + VariableType.NUMBER, + evaluation.setRhs, + evaluation, + ); + evaluation.reset(lhsValue, rhsValue); + return evaluation; } } @@ -69,6 +78,11 @@ class BinaryConditionEval implements ConditionEval { this._eval(); } + public reset (lhs: Value, rhs?: Value) { + this._operands = [lhs, rhs]; + this._eval(); + } + public setLhs (value: Value) { this._operands[0] = value; this._eval(); @@ -119,23 +133,27 @@ enum UnaryOperator { } @ccclass(`${CLASS_NAME_PREFIX_ANIM}UnaryCondition`) -export class UnaryCondition extends BindingHost implements Condition { +export class UnaryCondition implements Condition { public static readonly Operator = UnaryOperator; @serializable public operator!: UnaryOperator; @serializable - @parametric({ - notify: (value: Value, conditionEval: UnaryConditionEval) => conditionEval.setOperand(value), - }) - public operand!: Value; - - public [createEval] (context: { getParam(host: BindingHost, name: string): unknown; }) { - const { operator } = this; - const operand = context.getParam(this, 'operand') ?? this.operand; - validateConditionParamBoolean(operand, 'operand'); - return new UnaryConditionEval(operator, operand); + public operand = new BindableBoolean(); + + public [createEval] (context: ConditionEvalContext) { + const { operator, operand } = this; + const evaluation = new UnaryConditionEval(operator, 0.0); + const value = bindOr( + context, + operand, + VariableType.BOOLEAN, + evaluation.setOperand, + evaluation, + ); + evaluation.reset(value); + return evaluation; } } @@ -154,6 +172,10 @@ class UnaryConditionEval implements ConditionEval { this._eval(); } + public reset (value: Value) { + this.setOperand(value); + } + public setOperand (value: Value) { this._operand = value; this._eval(); @@ -181,19 +203,22 @@ class UnaryConditionEval implements ConditionEval { } @ccclass(`${CLASS_NAME_PREFIX_ANIM}TriggerCondition`) -export class TriggerCondition extends BindingHost implements Condition { - @parametric({ - notify: (value: Value, conditionEval: TriggerConditionEval) => { - validateConditionParamBoolean(value, 'trigger'); - conditionEval.trigger = value; - }, - }) +export class TriggerCondition implements Condition { + @serializable public trigger!: string; - [createEval] (context: { getParam(host: BindingHost, name: string): unknown; }): ConditionEval { - const value = context.getParam(this, 'trigger') ?? false; - validateConditionParamBoolean(value, 'trigger'); - return new TriggerConditionEval(value); + [createEval] (context: BindContext): ConditionEval { + const evaluation = new TriggerConditionEval(false); + const initialValue = context.bind( + this.trigger, + VariableType.TRIGGER, + evaluation.setTrigger, + evaluation, + ); + if (typeof initialValue !== 'undefined') { + evaluation.setTrigger(initialValue); + } + return evaluation; } } @@ -202,12 +227,8 @@ class TriggerConditionEval implements ConditionEval { this._triggered = triggered; } - get trigger () { - return this._triggered; - } - - set trigger (value) { - this._triggered = value; + public setTrigger (trigger: boolean) { + this._triggered = trigger; } public eval (): boolean { diff --git a/cocos/core/animation/newgen-anim/graph-eval.ts b/cocos/core/animation/newgen-anim/graph-eval.ts index 4a8b5790bae..8c98936b116 100644 --- a/cocos/core/animation/newgen-anim/graph-eval.ts +++ b/cocos/core/animation/newgen-anim/graph-eval.ts @@ -5,9 +5,9 @@ import { PoseEval, PoseEvalContext } from './pose'; import type { Node } from '../../scene-graph/node'; import { createEval } from './create-eval'; import { Value } from './variable'; -import { BindingHost, getPropertyBindingPoints } from './parametric'; +import { BindContext } from './parametric'; import { ConditionEval, TriggerCondition } from './condition'; -import { VariableNotDefinedError } from './errors'; +import { VariableNotDefinedError, VariableTypeMismatchedError } from './errors'; import { PoseNode } from './pose-node'; import { SkeletonMask } from '../skeleton-mask'; import { debug, warnID } from '../../platform/debug'; @@ -17,6 +17,7 @@ import { AnimationClip } from '../animation-clip'; import type { NewGenAnim } from './newgenanim-component'; import { StateMachineComponent } from './state-machine-component'; import { InteractiveGraphNode } from './graph-node'; +import { VariableType } from '.'; export class PoseGraphEval { private _varRefMap: Record = {}; @@ -28,8 +29,9 @@ export class PoseGraphEval { }; constructor (graph: PoseGraph, root: Node, newGenAnim: NewGenAnim) { - for (const [name, { value }] of graph.variables) { + for (const [name, { type, value }] of graph.variables) { this._varRefMap[name] = { + type, value, refs: [], }; @@ -43,17 +45,6 @@ export class PoseGraphEval { triggerResetFn: (name: string) => { this.setValue(name, false); }, - getParam: (host: BindingHost, name: string) => { - const varId = host.getPropertyBinding(name); - if (!varId) { - return undefined; - } - const varRefs = this._varRefMap[varId]; - if (!varRefs) { - throw new VariableNotDefinedError(varId); - } - return varRefs.value; - }, }; this._layerEvaluations = Array.from(graph.layers).map((layer) => { @@ -134,18 +125,28 @@ export class PoseGraphEval { } varRefs.value = value; - for (const { fn, args } of varRefs.refs) { - fn(value, ...args); + for (const { fn, thisArg, args } of varRefs.refs) { + fn.call(thisArg, value, ...args); } } - private _bind (varId: string, fn: (value: T, ...args: ExtraArgs) => void, args: ExtraArgs): T { + private _bind ( + varId: string, + type: VariableType, + fn: (this: TThis, value: T, ...args: ExtraArgs) => void, + thisArg: TThis, + ...args: ExtraArgs + ): T { const varRefs = this._varRefMap[varId]; if (!varRefs) { throw new VariableNotDefinedError(varId); } + if (type !== varRefs.type) { + throw new VariableTypeMismatchedError(varId, VariableType[type], VariableType[varRefs.type]); + } varRefs.refs.push({ fn: fn as (value: unknown, ...args: unknown[]) => T, + thisArg, args, }); return varRefs.value as unknown as T; @@ -172,7 +173,7 @@ export interface PoseNodeStats { type TriggerResetFn = (name: string) => void; -interface LayerContext { +interface LayerContext extends BindContext { newGenAnim: NewGenAnim; /** @@ -190,10 +191,6 @@ interface LayerContext { */ mask?: SkeletonMask; - getParam(host: BindingHost, name: string): unknown; - - bind(varId: string, fn: (value: T, ...args: ExtraArgs) => void, args: ExtraArgs): T; - /** * TODO: A little hacky. * A function which resets specified trigger. This function can be stored. @@ -407,10 +404,9 @@ class LayerEval { } transitionEval.conditions.forEach((conditionEval, iCondition) => { const condition = outgoing.conditions[iCondition]; - bindEvalProperties(context, condition, conditionEval); - if (condition instanceof TriggerCondition && condition.hasPropertyBinding('trigger')) { - const trigger = condition.getPropertyBinding('trigger'); - (transitionEval.triggers ??= []).push(trigger); + if (condition instanceof TriggerCondition && condition.trigger) { + // TODO: validates the existence of trigger? + (transitionEval.triggers ??= []).push(condition.trigger); } }); outgoingTransitions.push(transitionEval); @@ -943,19 +939,6 @@ const transitionMatchCacheRegular = new TransitionMatchCache(); const transitionMatchCacheAny = new TransitionMatchCache(); -function bindEvalProperties (context: LayerContext, source: T, evalObject: EvalT) { - const propertyBindingPoints = getPropertyBindingPoints(source); - if (!propertyBindingPoints) { - return; - } - for (const [bindingPointId, bindingPoint] of Object.entries(propertyBindingPoints)) { - const varName = source.getPropertyBinding(bindingPointId); - if (varName) { - context.bind(varName, bindingPoint.notify, [evalObject]); - } - } -} - enum NodeKind { entry, exit, any, pose, } @@ -1019,18 +1002,14 @@ interface SubgraphInfo { export class PoseNodeEval extends NodeBaseEval { constructor (node: PoseNode, context: LayerContext) { super(node); - this.speed = node.speed; - this.startRatio = node.startRatio; - bindEvalProperties(context, node, this); + this.speed = node.speed.value; + this.startRatio = node.startRatio.value; const poseEvalContext: PoseEvalContext = { ...context, - speed: node.speed, - startRatio: node.startRatio, + speed: node.speed.value, + startRatio: node.startRatio.value, }; const poseEval = node.pose?.[createEval](poseEvalContext) ?? null; - if (poseEval && node.pose instanceof BindingHost) { - bindEvalProperties(context, node.pose, poseEval); - } if (poseEval) { Object.defineProperty(poseEval, '__DEBUG_ID__', { value: this.name }); } @@ -1128,6 +1107,8 @@ interface TransitionEval { } interface VarRefs { + type: VariableType; + value: Value; refs: VarRef[]; @@ -1136,5 +1117,7 @@ interface VarRefs { interface VarRef { fn: (value: unknown, ...args: unknown[]) => void; + thisArg: unknown; + args: unknown[]; } diff --git a/cocos/core/animation/newgen-anim/index.ts b/cocos/core/animation/newgen-anim/index.ts index 7341c34681b..647f34627ed 100644 --- a/cocos/core/animation/newgen-anim/index.ts +++ b/cocos/core/animation/newgen-anim/index.ts @@ -11,7 +11,7 @@ export { PoseBlendDirect } from './pose-blend-direct'; export { PoseBlend1D } from './pose-blend-1d'; export { PoseBlend2D } from './pose-blend-2d'; export { NewGenAnim } from './newgenanim-component'; -export { getPropertyBindingPoints } from './parametric'; +export type { BindableNumber, BindableBoolean } from './parametric'; export { SkeletonMask } from '../skeleton-mask'; export { StateMachineComponent } from './state-machine-component'; diff --git a/cocos/core/animation/newgen-anim/parametric.ts b/cocos/core/animation/newgen-anim/parametric.ts index a516f4fc1a2..841af8d506e 100644 --- a/cocos/core/animation/newgen-anim/parametric.ts +++ b/cocos/core/animation/newgen-anim/parametric.ts @@ -1,124 +1,82 @@ +import { VariableType } from '.'; import { ccclass, serializable } from '../../data/decorators'; import { assertIsTrue } from '../../data/utils/asserts'; import { warn } from '../../platform/debug'; +import { CLASS_NAME_PREFIX_ANIM } from '../define'; -/** - * Describes a possibly parametric property. - * @param options - */ -export function parametric (options: { - notify: ParamNotify; -}): PropertyDecorator { - return (target, propertyName) => { - assertIsTrue(typeof propertyName === 'string'); - createBindingPoint(target, propertyName, options.notify); - }; -} +export interface Bindable { + value: TValue; -/** - * Describes a possibly parametric numeric property. - * @param options - */ -export function parametricNum (options: { - notify: ParamNotify; - min?: number; - max?: number; -}): PropertyDecorator { - return (target, propertyName) => { - assertIsTrue(typeof propertyName === 'string'); - createBindingPoint(target, propertyName, options.notify); - }; + variable: string; } -/** - * Would be called when the parametric property is changed. - */ -export type ParamNotify = (value: T, ...args: Args) => unknown; - -const propertyBindingPointsSymbol = Symbol('[[PropertyBindingPoints]]'); - -type BindingPointMap = Record>; - -interface BindingPointHost { - [propertyBindingPointsSymbol]: BindingPointMap; -} +@ccclass(`${CLASS_NAME_PREFIX_ANIM}BindableNumber`) +export class BindableNumber implements Bindable { + @serializable + public variable = ''; -interface PropertyBindingPoint { - /** - * The notify function. - */ - notify: ParamNotify; -} + @serializable + public value = 0.0; -function createBindingPoint (object: unknown, bindingPointId: string, notify: ParamNotify) { - const bindingPointMap = (object as Partial)[propertyBindingPointsSymbol] ??= {}; - bindingPointMap[bindingPointId] = { - notify: notify as ParamNotify, - }; + constructor (value = 0.0) { + this.value = value; + } } -const propertyBindingsSymbol = Symbol('[[PropertyBindings]]'); - -@ccclass('cc.animation.BindingHost') -export class BindingHost { +@ccclass(`${CLASS_NAME_PREFIX_ANIM}BindableBoolean`) +export class BindableBoolean implements Bindable { @serializable - private _bindings: Record = {}; + public variable = ''; - get [propertyBindingsSymbol] () { - return this._bindings; - } + @serializable + public value = false; - /** - * Binds variable onto the property binding point of this binding host. - * @param bindingPointId The property binding point to bind. - * @param varName The variable name. - */ - public bindProperty (bindingPointId: string, varName: string) { - const bindingPoint = this[propertyBindingPointsSymbol]?.[bindingPointId]; - if (!bindingPoint) { - warn(`${bindingPointId} is not a binding point.`); - return; - } - const bindingMap = this[propertyBindingsSymbol]; - bindingMap[bindingPointId] = varName; + constructor (value = false) { + this.value = value; } +} - /** - * Unbinds property. - * @param bindingPointId The property binding point to bind. - */ - public unbindProperty (bindingPointId: string) { - const bindingPoint = this[propertyBindingPointsSymbol]?.[bindingPointId]; - if (!bindingPoint) { - return; - } - delete this[propertyBindingsSymbol][bindingPointId]; - } +export type BindCallback = + (this: TThis, value: TValue, ...args: TArgs) => void; + +export interface BindContext { + bind( + variable: string, + type: VariableType, + callback: BindCallback, + thisArg: TThis, + ...args: TArgs + ): TValue | undefined; +} - /** - * Returns if specified variable has property binding. - * @param bindingPointId The property binding point to bind. - * @param bindingPointId - */ - public hasPropertyBinding (bindingPointId: string) { - return !!this[propertyBindingsSymbol][bindingPointId]; +export function bindOr ( + context: BindContext, + bindable: Bindable, + type: VariableType, + callback: BindCallback, + thisArg: TThis, + ...args: TArgs +) { + const { + variable, + value, + } = bindable; + + if (!bindable.variable) { + return bindable.value; } - /** - * Gets the property binding on the specified property binding point. - * @param bindingPointId The property binding point to bind. - * @returns The name of the bounded variable, if one exists. - */ - public getPropertyBinding (bindingPointId: string): string { - return this[propertyBindingsSymbol][bindingPointId]; + const initialValue = context.bind( + variable, + type, + callback, + thisArg, + ...args, + ); + + if (typeof initialValue === 'undefined') { + return value; + } else { + return initialValue; } } - -/** - * Gets all property binding points on the object. - * @param object The object. - * @returns All property binding points. Keys are property binding point id and values are bound variable name. - */ -export function getPropertyBindingPoints (object: unknown): Record> | undefined { - return (object as Partial)[propertyBindingPointsSymbol]; -} diff --git a/cocos/core/animation/newgen-anim/pose-blend-1d.ts b/cocos/core/animation/newgen-anim/pose-blend-1d.ts index 8d8c8ad41cd..4ba96971301 100644 --- a/cocos/core/animation/newgen-anim/pose-blend-1d.ts +++ b/cocos/core/animation/newgen-anim/pose-blend-1d.ts @@ -1,13 +1,13 @@ import { serializable } from 'cc.decorator'; import { ccclass } from '../../data/class-decorator'; import { createEval } from './create-eval'; -import { BindingHost, parametricNum } from './parametric'; +import { BindableNumber, bindOr } from './parametric'; import { Pose, PoseEval, PoseEvalContext } from './pose'; import { PoseBlend, PoseBlendEval, validateBlendParam } from './pose-blend'; import { blend1D } from './blend-1d'; @ccclass('cc.animation.Blender1D') -export class PoseBlend1D extends BindingHost implements PoseBlend { +export class PoseBlend1D implements PoseBlend { @serializable protected _poses: (Pose | null)[] = []; @@ -15,11 +15,7 @@ export class PoseBlend1D extends BindingHost implements PoseBlend { private _thresholds: number[] = []; @serializable - private _param = 0.0; - - constructor () { - super(); - } + public param = new BindableNumber(); get children () { return this._listChildren(); @@ -31,24 +27,17 @@ export class PoseBlend1D extends BindingHost implements PoseBlend { this._thresholds = sorted.map(([, threshold]) => threshold); } - @parametricNum<[PoseBlend1DEval]>({ - notify: (value, host) => { - validateBlendParam(value, 'param'); - host.setInput(value, 0); - }, - }) - get param () { - return this._param; - } - - set param (value: number) { - this._param = value; - } - public [createEval] (context: PoseEvalContext) { - const param = context.getParam(this, 'param') ?? this._param; - validateBlendParam(param, 'param'); - return new PoseBlend1DEval(context, this._poses, this._thresholds, param); + const evaluation = new PoseBlend1DEval(context, this._poses, this._thresholds, 0.0); + const initialValue = bindOr( + context, + this.param, + evaluation.setInput, + evaluation, + 0, + ); + evaluation.setInput(initialValue, 0); + return evaluation; } private* _listChildren (): Iterable<[Pose | null, number]> { diff --git a/cocos/core/animation/newgen-anim/pose-blend-2d.ts b/cocos/core/animation/newgen-anim/pose-blend-2d.ts index afb517ad0fb..7fec8974e0e 100644 --- a/cocos/core/animation/newgen-anim/pose-blend-2d.ts +++ b/cocos/core/animation/newgen-anim/pose-blend-2d.ts @@ -5,7 +5,7 @@ import { createEval } from './create-eval'; import { PoseBlend, PoseBlendEval, validateBlendParam } from './pose-blend'; import { Pose, PoseEvalContext } from './pose'; import { serializable, type } from '../../data/decorators'; -import { BindingHost, parametricNum } from './parametric'; +import { BindableNumber, bindOr } from './parametric'; import { sampleFreeformCartesian, sampleFreeformDirectional, blendSimpleDirectional } from './blend-2d'; enum Algorithm { @@ -17,7 +17,7 @@ enum Algorithm { ccenum(Algorithm); @ccclass('cc.animation.Blender2D') -export class PoseBlend2D extends BindingHost implements PoseBlend { +export class PoseBlend2D implements PoseBlend { public static Algorithm = Algorithm; @serializable @@ -30,14 +30,10 @@ export class PoseBlend2D extends BindingHost implements PoseBlend { private _thresholds: Vec2[] = []; @serializable - private _paramX = 0.0; + public paramX = new BindableNumber(); @serializable - private _paramY = 0.0; - - constructor () { - super(); - } + public paramY = new BindableNumber(); get children () { return this._listChildren(); @@ -49,7 +45,6 @@ export class PoseBlend2D extends BindingHost implements PoseBlend { this._thresholds = childArray.map(([, threshold]) => threshold); } - @property get thresholds () { return this._thresholds; } @@ -58,40 +53,25 @@ export class PoseBlend2D extends BindingHost implements PoseBlend { this._thresholds = thresholds.slice().map((threshold) => threshold.clone()); } - @parametricNum<[PoseBlend2DDEval]>({ - notify: (value, host) => { - validateBlendParam(value, 'paramX'); - host.setInput(value, 0); - }, - }) - get paramX () { - return this._paramX; - } - - set paramX (value: number) { - this._paramX = value; - } - - @parametricNum<[PoseBlend2DDEval]>({ - notify: (value, host) => { - validateBlendParam(value, 'paramY'); - host.setInput(value, 1); - }, - }) - get paramY () { - return this._paramY; - } - - set paramY (value: number) { - this._paramY = value; - } - public [createEval] (context: PoseEvalContext) { - const paramX = context.getParam(this, 'paramX') ?? this._paramX; - validateBlendParam(paramX, 'paramX'); - const paramY = context.getParam(this, 'paramY') ?? this._paramY; - validateBlendParam(paramY, 'paramY'); - return new PoseBlend2DDEval(context, this.poses, this.thresholds, this.algorithm, [paramX, paramY]); + const evaluation = new PoseBlend2DDEval(context, this.poses, this.thresholds, this.algorithm, [0.0, 0.0]); + const initialValueX = bindOr( + context, + this.paramX, + evaluation.setInput, + evaluation, + 0, + ); + const initialValueY = bindOr( + context, + this.paramY, + evaluation.setInput, + evaluation, + 1, + ); + evaluation.setInput(initialValueX, 0); + evaluation.setInput(initialValueY, 1); + return evaluation; } private* _listChildren (): Iterable<[Pose | null, Vec2]> { diff --git a/cocos/core/animation/newgen-anim/pose-node.ts b/cocos/core/animation/newgen-anim/pose-node.ts index 0625d760a02..3bebe967f41 100644 --- a/cocos/core/animation/newgen-anim/pose-node.ts +++ b/cocos/core/animation/newgen-anim/pose-node.ts @@ -1,7 +1,7 @@ import { ccclass, serializable } from 'cc.decorator'; import { Pose } from './pose'; import { GraphNode, InteractiveGraphNode } from './graph-node'; -import { parametric, parametricNum } from './parametric'; +import { BindableNumber } from './parametric'; import { PoseNodeEval } from './graph-eval'; @ccclass('cc.animation.PoseNode') @@ -10,20 +10,10 @@ export class PoseNode extends InteractiveGraphNode { public pose: Pose | null = null; @serializable - @parametricNum<[PoseNodeEval]>({ - notify: (value, poseNodeEval) => { - poseNodeEval.startRatio = value; - }, - }) - public startRatio = 0.0; + public startRatio = new BindableNumber(); @serializable - @parametricNum<[PoseNodeEval]>({ - notify: (value, poseNodeEval) => { - poseNodeEval.speed = value; - }, - }) - public speed = 1.0; + public speed = new BindableNumber(1.0); @serializable public loop = true; diff --git a/cocos/core/animation/newgen-anim/pose.ts b/cocos/core/animation/newgen-anim/pose.ts index 52b5696fbfb..b2e7f8b1074 100644 --- a/cocos/core/animation/newgen-anim/pose.ts +++ b/cocos/core/animation/newgen-anim/pose.ts @@ -1,11 +1,11 @@ import { Node } from '../../scene-graph'; import { SkeletonMask } from '../skeleton-mask'; import { createEval } from './create-eval'; -import type { BindingHost } from './parametric'; +import type { BindContext } from './parametric'; import type { BlendStateBuffer } from '../../../3d/skeletal-animation/skeletal-animation-blending'; import type { PoseStatus } from './graph-eval'; -export interface PoseEvalContext { +export interface PoseEvalContext extends BindContext { node: Node; blendBuffer: BlendStateBuffer; @@ -15,8 +15,6 @@ export interface PoseEvalContext { speed: number; startRatio: number; - - getParam(host: BindingHost, name: string): unknown; } export interface PoseEval { diff --git a/tests/animation/newgenanim.test.ts b/tests/animation/newgenanim.test.ts index 3e2491320c4..7b1cdcd33da 100644 --- a/tests/animation/newgenanim.test.ts +++ b/tests/animation/newgenanim.test.ts @@ -15,7 +15,6 @@ import gVariableNotFoundInPoseBlend from './graphs/variable-not-found-in-pose-bl import gPoseBlendRequiresNumbers from './graphs/pose-blend-requires-numbers'; import gInfinityLoop from './graphs/infinity-loop'; import gZeroTimePiece from './graphs/zero-time-piece'; -import { getPropertyBindingPoints } from '../../cocos/core/animation/newgen-anim/parametric'; import { blend1D } from '../../cocos/core/animation/newgen-anim/blend-1d'; import '../utils/matcher-deep-close-to'; import { BinaryCondition, UnaryCondition, TriggerCondition } from '../../cocos/core/animation/newgen-anim/condition'; @@ -37,10 +36,12 @@ describe('NewGen Anim', () => { const graphNode = layerGraph.addPoseNode(); expect(graphNode.name).toBe(''); - expect(graphNode.speed).toBe(1.0); + expect(graphNode.speed.variable).toBe(''); + expect(graphNode.speed.value).toBe(1.0); expect(graphNode.loop).toBe(true); expect(graphNode.pose).toBeNull(); - expect(graphNode.startRatio).toBe(0.0); + expect(graphNode.startRatio.variable).toBe(''); + expect(graphNode.startRatio.value).toBe(0.0); testGraphDefaults(layerGraph.addSubgraph()); @@ -49,13 +50,16 @@ describe('NewGen Anim', () => { const poseBlend1D = new PoseBlend1D(); expect(Array.from(poseBlend1D.children)).toHaveLength(0); - expect(poseBlend1D.param).toBe(0.0); + expect(poseBlend1D.param.variable).toBe(''); + expect(poseBlend1D.param.value).toBe(0.0); const poseBlend2D = new PoseBlend2D(); expect(poseBlend2D.algorithm).toBe(PoseBlend2D.Algorithm.SIMPLE_DIRECTIONAL); expect(Array.from(poseBlend2D.children)).toHaveLength(0); - expect(poseBlend2D.paramX).toBe(0.0); - expect(poseBlend2D.paramY).toBe(0.0); + expect(poseBlend2D.paramX.variable).toBe(''); + expect(poseBlend2D.paramX.value).toBe(0.0); + expect(poseBlend2D.paramY.variable).toBe(''); + expect(poseBlend2D.paramY.value).toBe(0.0); const poseBlendDirect = new PoseBlendDirect(); expect(Array.from(poseBlendDirect.children)).toHaveLength(0); @@ -260,7 +264,7 @@ describe('NewGen Anim', () => { const subgraphEntryToExit = subgraph.connect(subgraph.entryNode, subgraph.exitNode); const [subgraphEntryToExitCondition] = subgraphEntryToExit.conditions = [new TriggerCondition()]; poseGraph.addVariable('subgraphExitTrigger', VariableType.TRIGGER, false); - subgraphEntryToExitCondition.bindProperty('trigger', 'subgraphExitTrigger'); + subgraphEntryToExitCondition.trigger = 'subgraphExitTrigger'; graph.connect(graph.entryNode, subgraph); const node = graph.addPoseNode(); @@ -269,7 +273,7 @@ describe('NewGen Anim', () => { const [triggerCondition] = subgraphToNode.conditions = [new TriggerCondition()]; poseGraph.addVariable('trigger', VariableType.TRIGGER); - triggerCondition.bindProperty('trigger', 'trigger'); + triggerCondition.trigger = 'trigger'; const graphEval = createPoseGraphEval(poseGraph, new Node()); @@ -575,7 +579,7 @@ describe('NewGen Anim', () => { for (const [input, output] of samples) { const condition = new UnaryCondition(); condition.operator = op; - condition.operand = input; + condition.operand.value = input; const graph = createPoseGraphForConditionTest([condition]); const graphEval = createPoseGraphEval(graph, new Node()); graphEval.update(0.0); @@ -628,8 +632,8 @@ describe('NewGen Anim', () => { for (const [lhs, rhs, output] of samples) { const condition = new BinaryCondition(); condition.operator = op; - condition.lhs = lhs; - condition.rhs = rhs; + condition.lhs.value = lhs; + condition.rhs.value = rhs; const graph = createPoseGraphForConditionTest([condition]); const graphEval = createPoseGraphEval(graph, new Node()); graphEval.update(0.0); @@ -647,7 +651,7 @@ describe('NewGen Anim', () => { test(`Trigger condition`, () => { const condition = new TriggerCondition(); - condition.bindProperty('trigger', 'theTrigger'); + condition.trigger = 'theTrigger'; const poseGraph = new PoseGraph(); const layer = poseGraph.addLayer(); const graph = layer.graph; @@ -702,7 +706,7 @@ describe('NewGen Anim', () => { const addTriggerCondition = (transition: Transition) => { const [condition] = transition.conditions = [new TriggerCondition()]; - condition.bindProperty('trigger', `trigger${nTriggers}`); + condition.trigger = `trigger${nTriggers}`; poseGraph.addVariable(`trigger${nTriggers}`, VariableType.TRIGGER); ++nTriggers; }; @@ -762,13 +766,13 @@ describe('NewGen Anim', () => { transition1.exitCondition = 0.8; const [ transition1Condition ] = transition1.conditions = [ new UnaryCondition() ]; transition1Condition.operator = UnaryCondition.Operator.TRUTHY; - transition1Condition.bindProperty('operand', 'switch1'); + transition1Condition.operand.variable = 'switch1'; const transition2 = graph.connect(poseNode1, poseNode3); transition2.exitConditionEnabled = true; transition2.exitCondition = 0.8; const [ transition2Condition ] = transition2.conditions = [ new UnaryCondition() ]; transition2Condition.operator = UnaryCondition.Operator.TRUTHY; - transition2Condition.bindProperty('operand', 'switch2'); + transition2Condition.operand.variable = 'switch2'; graph.connect(graph.entryNode, poseNode1); poseGraph.addVariable('switch1', VariableType.BOOLEAN, false); poseGraph.addVariable('switch2', VariableType.BOOLEAN, false); @@ -841,6 +845,7 @@ describe('NewGen Anim', () => { const poseNodeClip = poseNode.pose = createPosePositionX(1.0, 0.5, 'PoseNodeClip'); const subgraph = layerGraph.addSubgraph(); + subgraph.name = 'Subgraph'; const subgraphPoseNode = subgraph.addPoseNode(); const subgraphPoseNodeClip = subgraphPoseNode.pose = createPosePositionX(1.0, 0.7, 'SubgraphPoseNodeClip'); subgraph.connect(subgraph.entryNode, subgraphPoseNode); @@ -851,7 +856,7 @@ describe('NewGen Anim', () => { anyTransition.exitConditionEnabled = true; anyTransition.exitCondition = 0.1; const [ triggerCondition ] = anyTransition.conditions = [new TriggerCondition()]; - triggerCondition.bindProperty('trigger', 'trigger'); + triggerCondition.trigger = 'trigger'; graph.addVariable('trigger', VariableType.TRIGGER, true); const graphEval = createPoseGraphEval(graph, new Node()); @@ -1057,7 +1062,7 @@ describe('NewGen Anim', () => { const layerGraph = layer.graph; const poseNode = layerGraph.addPoseNode(); poseNode.pose = createPosePositionXLinear(1.0, 0.3, 1.7); - poseNode.speed = 1.2; + poseNode.speed.value = 1.2; layerGraph.connect(layerGraph.entryNode, poseNode); const node = new Node(); @@ -1167,22 +1172,6 @@ describe('NewGen Anim', () => { }); describe('Property binding', () => { - test('Bind property', () => { - const poseBlend2D = new PoseBlend2D(); - const bindingPoints = getPropertyBindingPoints(poseBlend2D); - expect(Object.keys(bindingPoints)).toEqual(expect.arrayContaining([ - 'paramX', - 'paramY', - ])); - poseBlend2D.bindProperty('paramX', 'x'); - expect(poseBlend2D.getPropertyBinding('paramX')).toBe('x'); - }); - - test('Serialization', () => { - const poseBlend2D = new PoseBlend2D(); - poseBlend2D.bindProperty('paramX', 'x'); - expect(poseBlend2D.getPropertyBinding('paramX')).toBe('x'); - }); }); });