@neocompose/cli 0.74.0 → 0.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.75.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0a7ec0e: `member.OnChanged.Clear()` removes every handler from a member's or getter's listener set. On a stored member it clears `@listeners` defaults and Save and Session wiring. On a getter it ends the subscription. Any other use of `OnChanged` as a value now reports "OnChanged supports only '+=', '-=', and Clear()." instead of "Cannot read member 'OnChanged'". Exports move to schema 41, so Unity needs SDK 0.65.0.
|
|
8
|
+
|
|
3
9
|
## 0.74.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/dist/neo.mjs
CHANGED
|
@@ -3150,24 +3150,30 @@ function isNSInstructionTry(value) {
|
|
|
3150
3150
|
}
|
|
3151
3151
|
return true;
|
|
3152
3152
|
}
|
|
3153
|
-
function
|
|
3154
|
-
const
|
|
3155
|
-
if (instruction?.type !== "addChangeListener" /* addChangeListener */ && instruction?.type !== "removeChangeListener" /* removeChangeListener */)
|
|
3156
|
-
return false;
|
|
3157
|
-
const target = instruction.target;
|
|
3153
|
+
function isNSChangeListenerTarget(value) {
|
|
3154
|
+
const target = value;
|
|
3158
3155
|
if (!target || typeof target.memberId !== "string" || target.memberId.length === 0)
|
|
3159
3156
|
return false;
|
|
3160
3157
|
if (!isNSPointer(target.owner) || !isNSTypeInfo(target.typeInfo))
|
|
3161
3158
|
return false;
|
|
3162
|
-
|
|
3163
|
-
|
|
3159
|
+
return target.writability !== void 0 && Object.values(NSWritability).includes(target.writability);
|
|
3160
|
+
}
|
|
3161
|
+
function isNSInstructionChangeListener(value) {
|
|
3162
|
+
const instruction = value;
|
|
3163
|
+
if (instruction?.type !== "addChangeListener" /* addChangeListener */ && instruction?.type !== "removeChangeListener" /* removeChangeListener */)
|
|
3164
|
+
return false;
|
|
3165
|
+
return isNSChangeListenerTarget(instruction.target) && isNSPointer(instruction.listener);
|
|
3166
|
+
}
|
|
3167
|
+
function isNSInstructionClearChangeListeners(value) {
|
|
3168
|
+
const instruction = value;
|
|
3169
|
+
return instruction?.type === "clearChangeListeners" /* clearChangeListeners */ && isNSChangeListenerTarget(instruction.target);
|
|
3164
3170
|
}
|
|
3165
3171
|
function isNSInstructionDebug(value) {
|
|
3166
3172
|
const v = value;
|
|
3167
3173
|
return v?.type === "debug" /* debug */ && ["log", "warning", "error", "assert"].includes(v.severity ?? "") && isNSPointer(v.message) && isNSTypeInfo(v.messageType) && (v.severity === "assert" ? isNSPointer(v.condition) : v.condition === void 0);
|
|
3168
3174
|
}
|
|
3169
3175
|
function isNSInstruction(value) {
|
|
3170
|
-
return isNSInstructionDebug(value) || isNSInstructionVariable(value) || isNSInstructionIfBranch(value) || isNSInstructionReturn(value) || isNSInstructionThrow(value) || isNSInstructionAssign(value) || isNSInstructionCollectionCall(value) || isNSInstructionFunctionCall(value) || isNSInstructionConditionLoop(value) || isNSInstructionFor(value) || isNSInstructionForEach(value) || isNSInstructionBreak(value) || isNSInstructionContinue(value) || isNSInstructionSwitch(value) || isNSInstructionTry(value) || isNSInstructionChangeListener(value) || isNSInstructionAddActionListener(value) || isNSInstructionRemoveActionListener(value);
|
|
3176
|
+
return isNSInstructionDebug(value) || isNSInstructionVariable(value) || isNSInstructionIfBranch(value) || isNSInstructionReturn(value) || isNSInstructionThrow(value) || isNSInstructionAssign(value) || isNSInstructionCollectionCall(value) || isNSInstructionFunctionCall(value) || isNSInstructionConditionLoop(value) || isNSInstructionFor(value) || isNSInstructionForEach(value) || isNSInstructionBreak(value) || isNSInstructionContinue(value) || isNSInstructionSwitch(value) || isNSInstructionTry(value) || isNSInstructionChangeListener(value) || isNSInstructionClearChangeListeners(value) || isNSInstructionAddActionListener(value) || isNSInstructionRemoveActionListener(value);
|
|
3171
3177
|
}
|
|
3172
3178
|
function isNSInstructions(value) {
|
|
3173
3179
|
return Array.isArray(value) && value.every(isNSInstruction);
|
|
@@ -30812,7 +30818,7 @@ function intersectCollectionWritability(left, right) {
|
|
|
30812
30818
|
const writable = (value) => value === "local" /* Local */ || value === "save" /* Save */ || value === "session" /* Session */ || value === "runtime" /* Runtime */;
|
|
30813
30819
|
return writable(left) && writable(right) ? "runtime" /* Runtime */ : "readOnly" /* ReadOnly */;
|
|
30814
30820
|
}
|
|
30815
|
-
var CONSTRUCTING_FUNCTION_KINDS, VARIANT_INITIALIZE_NAME, VARIANT_APPLY_NAME, MATH_NAMESPACE_NAME, MATH_FUNCTIONS, MATH_CONSTANTS, LIST_STATIC_FUNCTIONS, WRITE_TARGET_DESCRIPTIONS, FALLTHROUGH_LABELS, Scope, UnresolvedPersistedListenerReference, StrictNeoScriptResolver, SPRITE_DERIVED_MEMBERS;
|
|
30821
|
+
var CONSTRUCTING_FUNCTION_KINDS, VARIANT_INITIALIZE_NAME, VARIANT_APPLY_NAME, MATH_NAMESPACE_NAME, MATH_FUNCTIONS, MATH_CONSTANTS, LIST_STATIC_FUNCTIONS, WRITE_TARGET_DESCRIPTIONS, ON_CHANGED_OPERATIONS_ERROR, FALLTHROUGH_LABELS, Scope, UnresolvedPersistedListenerReference, StrictNeoScriptResolver, SPRITE_DERIVED_MEMBERS;
|
|
30816
30822
|
var init_strict_resolver = __esm({
|
|
30817
30823
|
"../packages/neoscript-language/src/strict-resolver.ts"() {
|
|
30818
30824
|
"use strict";
|
|
@@ -30873,6 +30879,7 @@ var init_strict_resolver = __esm({
|
|
|
30873
30879
|
projectValue: "a stored project value",
|
|
30874
30880
|
unknown: "something other than `this` or a local"
|
|
30875
30881
|
};
|
|
30882
|
+
ON_CHANGED_OPERATIONS_ERROR = "OnChanged supports only '+=', '-=', and Clear().";
|
|
30876
30883
|
FALLTHROUGH_LABELS = {
|
|
30877
30884
|
function: "NSFunction",
|
|
30878
30885
|
getter: "getter",
|
|
@@ -32215,6 +32222,12 @@ var init_strict_resolver = __esm({
|
|
|
32215
32222
|
this.resolveExpression(statement.expr, scope);
|
|
32216
32223
|
const debug = this.resolveDebugStatement(statement.expr, scope);
|
|
32217
32224
|
if (debug) return debug;
|
|
32225
|
+
const listenerClear = this.resolveChangeListenerClear(
|
|
32226
|
+
statement.expr,
|
|
32227
|
+
scope,
|
|
32228
|
+
statement.pos
|
|
32229
|
+
);
|
|
32230
|
+
if (listenerClear) return listenerClear;
|
|
32218
32231
|
const actionClear = this.resolveActionClear(
|
|
32219
32232
|
statement.expr,
|
|
32220
32233
|
scope,
|
|
@@ -32575,8 +32588,20 @@ var init_strict_resolver = __esm({
|
|
|
32575
32588
|
const observed = this.resolveExpression(statement.target.receiver, scope);
|
|
32576
32589
|
const observedType = neoScriptMemberLookupType(observed.type);
|
|
32577
32590
|
const declared = observedType.kind === "named" && this.project.typeById.get(observedType.typeId)?.members.some((member) => member.name === "OnChanged");
|
|
32578
|
-
if (!declared)
|
|
32579
|
-
|
|
32591
|
+
if (!declared) {
|
|
32592
|
+
if (statement.op !== "+=" && statement.op !== "-=")
|
|
32593
|
+
throw new CompileError(ON_CHANGED_OPERATIONS_ERROR, statement.pos);
|
|
32594
|
+
return this.resolveChangeListener(
|
|
32595
|
+
statement.target,
|
|
32596
|
+
observed,
|
|
32597
|
+
scope,
|
|
32598
|
+
statement.pos,
|
|
32599
|
+
{
|
|
32600
|
+
kind: statement.op === "+=" ? "addChangeListener" /* AddChangeListener */ : "removeChangeListener" /* RemoveChangeListener */,
|
|
32601
|
+
handler: statement.value
|
|
32602
|
+
}
|
|
32603
|
+
);
|
|
32604
|
+
}
|
|
32580
32605
|
}
|
|
32581
32606
|
const local = statement.target.kind === "ident" ? scope.lookup(statement.target.name) : null;
|
|
32582
32607
|
if (local?.readonlyBinding) {
|
|
@@ -32760,158 +32785,142 @@ var init_strict_resolver = __esm({
|
|
|
32760
32785
|
pos
|
|
32761
32786
|
);
|
|
32762
32787
|
}
|
|
32763
|
-
|
|
32764
|
-
|
|
32765
|
-
|
|
32766
|
-
|
|
32767
|
-
|
|
32768
|
-
|
|
32769
|
-
|
|
32770
|
-
if (
|
|
32788
|
+
/** `X.M.OnChanged.Clear()` empties M's listener set on X (P70, P106). */
|
|
32789
|
+
resolveChangeListenerClear(expression, scope, pos) {
|
|
32790
|
+
if (expression.kind !== "call" || expression.callee.kind !== "member" || expression.callee.name !== "Clear" || expression.callee.receiver.kind !== "member" || expression.callee.receiver.name !== "OnChanged")
|
|
32791
|
+
return null;
|
|
32792
|
+
const onChanged = expression.callee.receiver;
|
|
32793
|
+
const observed = this.resolveExpression(onChanged.receiver, scope);
|
|
32794
|
+
const observedType = neoScriptMemberLookupType(observed.type);
|
|
32795
|
+
if (observedType.kind === "named" && this.project.typeById.get(observedType.typeId)?.members.some((member) => member.name === "OnChanged"))
|
|
32796
|
+
return null;
|
|
32797
|
+
if (expression.callee.optional)
|
|
32771
32798
|
throw new CompileError(
|
|
32772
|
-
"OnChanged
|
|
32773
|
-
|
|
32799
|
+
"OnChanged is never null, so optional-chained Clear is not supported.",
|
|
32800
|
+
pos
|
|
32774
32801
|
);
|
|
32775
|
-
|
|
32776
|
-
|
|
32802
|
+
requireArgCount("Clear", expression.args, 0, pos);
|
|
32803
|
+
return this.resolveChangeListener(onChanged, observed, scope, pos, {
|
|
32804
|
+
kind: "clearChangeListeners" /* ClearChangeListeners */
|
|
32805
|
+
});
|
|
32806
|
+
}
|
|
32807
|
+
resolveChangeListener(onChanged, observed, scope, pos, edit) {
|
|
32808
|
+
if (onChanged.optional) {
|
|
32777
32809
|
throw new CompileError(
|
|
32778
32810
|
"OnChanged cannot use an optional subscription target.",
|
|
32779
|
-
|
|
32811
|
+
pos
|
|
32780
32812
|
);
|
|
32781
32813
|
}
|
|
32782
32814
|
if (observed.optionalChainType !== void 0) {
|
|
32783
32815
|
throw new CompileError(
|
|
32784
32816
|
"OnChanged cannot subscribe through optional chaining; narrow the receiver first.",
|
|
32785
|
-
|
|
32817
|
+
pos
|
|
32786
32818
|
);
|
|
32787
32819
|
}
|
|
32788
32820
|
const member = observed.symbol;
|
|
32789
32821
|
if (!member)
|
|
32790
32822
|
throw new CompileError(
|
|
32791
32823
|
"OnChanged requires a declared member, not a local variable.",
|
|
32792
|
-
|
|
32824
|
+
pos
|
|
32793
32825
|
);
|
|
32794
32826
|
if (observed.pointer.type === "staticMember" /* StaticMember */ || observed.pointer.type === "callGetter" /* CallGetter */ && observed.pointer.receiver.kind === "static") {
|
|
32795
|
-
const access =
|
|
32827
|
+
const access = onChanged.receiver;
|
|
32796
32828
|
const name = access.kind === "member" && access.receiver.kind === "ident" ? `${access.receiver.name}.${member.name}` : member.name;
|
|
32797
32829
|
throw new CompileError(
|
|
32798
32830
|
`OnChanged requires an instance member. '${name}' is static.`,
|
|
32799
|
-
|
|
32831
|
+
pos
|
|
32800
32832
|
);
|
|
32801
32833
|
}
|
|
32802
32834
|
if (observed.pointer.type === "callGetter" /* CallGetter */)
|
|
32803
|
-
return this.
|
|
32804
|
-
|
|
32805
|
-
statement.target.receiver,
|
|
32835
|
+
return this.resolveGetterChangeListener(
|
|
32836
|
+
onChanged.receiver,
|
|
32806
32837
|
observed,
|
|
32807
32838
|
observed.pointer,
|
|
32808
32839
|
member,
|
|
32809
|
-
scope
|
|
32840
|
+
scope,
|
|
32841
|
+
pos,
|
|
32842
|
+
edit
|
|
32810
32843
|
);
|
|
32811
32844
|
if (observed.pointer.type !== "keyOf" /* KeyOf */)
|
|
32812
|
-
throw new CompileError(
|
|
32813
|
-
"OnChanged requires a declared member slot.",
|
|
32814
|
-
statement.pos
|
|
32815
|
-
);
|
|
32845
|
+
throw new CompileError("OnChanged requires a declared member slot.", pos);
|
|
32816
32846
|
if (!observed.pointer.memberId)
|
|
32817
32847
|
throw new CompileError(
|
|
32818
32848
|
"OnChanged cannot observe a collection entry.",
|
|
32819
|
-
|
|
32849
|
+
pos
|
|
32820
32850
|
);
|
|
32821
32851
|
if (member.computed) {
|
|
32822
32852
|
throw new CompileError(
|
|
32823
32853
|
"OnChanged cannot observe a computed member.",
|
|
32824
|
-
|
|
32854
|
+
pos
|
|
32825
32855
|
);
|
|
32826
32856
|
}
|
|
32827
32857
|
if (member.readonlyDeclaration) {
|
|
32828
32858
|
throw new CompileError(
|
|
32829
32859
|
"OnChanged cannot observe a readonly declaration.",
|
|
32830
|
-
|
|
32860
|
+
pos
|
|
32831
32861
|
);
|
|
32832
32862
|
}
|
|
32833
32863
|
if (observed.type.kind === "delegate" || observed.type.kind === "action") {
|
|
32834
32864
|
throw new CompileError(
|
|
32835
32865
|
"OnChanged cannot observe a callable member.",
|
|
32836
|
-
|
|
32866
|
+
pos
|
|
32837
32867
|
);
|
|
32838
32868
|
}
|
|
32839
32869
|
const writability = observed.userData ? "runtime" /* Runtime */ : observed.writability;
|
|
32840
32870
|
if (writability !== "save" /* Save */ && writability !== "session" /* Session */ && writability !== "runtime" /* Runtime */ && writability !== "local" /* Local */) {
|
|
32841
32871
|
throw new CompileError(
|
|
32842
32872
|
"OnChanged requires a member whose storage is writable at gameplay runtime.",
|
|
32843
|
-
|
|
32873
|
+
pos
|
|
32844
32874
|
);
|
|
32845
32875
|
}
|
|
32846
32876
|
this.validateConstructorWriteTarget(
|
|
32847
32877
|
observed.pointer,
|
|
32848
|
-
"subscribe to",
|
|
32849
|
-
|
|
32878
|
+
edit.kind === "clearChangeListeners" /* ClearChangeListeners */ ? "clear listeners on" : "subscribe to",
|
|
32879
|
+
pos,
|
|
32850
32880
|
scope,
|
|
32851
32881
|
false
|
|
32852
32882
|
);
|
|
32853
|
-
if (!statement.value) {
|
|
32854
|
-
throw new CompileError(
|
|
32855
|
-
"OnChanged subscription requires a handler member.",
|
|
32856
|
-
statement.pos
|
|
32857
|
-
);
|
|
32858
|
-
}
|
|
32859
32883
|
const storageType = this.assignmentStorageType(
|
|
32860
|
-
|
|
32884
|
+
onChanged.receiver,
|
|
32861
32885
|
observed,
|
|
32862
32886
|
null,
|
|
32863
32887
|
scope
|
|
32864
32888
|
);
|
|
32865
|
-
|
|
32866
|
-
|
|
32867
|
-
|
|
32868
|
-
};
|
|
32869
|
-
const listener = this.resolveActionListener(
|
|
32870
|
-
statement.value,
|
|
32871
|
-
expected,
|
|
32872
|
-
scope,
|
|
32873
|
-
true,
|
|
32874
|
-
true
|
|
32875
|
-
);
|
|
32876
|
-
return {
|
|
32877
|
-
type: statement.op === "+=" ? "addChangeListener" /* AddChangeListener */ : "removeChangeListener" /* RemoveChangeListener */,
|
|
32878
|
-
target: {
|
|
32889
|
+
return this.changeListenerInstruction(
|
|
32890
|
+
edit,
|
|
32891
|
+
{
|
|
32879
32892
|
owner: observed.pointer.keyOf.pointer,
|
|
32880
32893
|
memberId: member.canonicalMemberId ?? observed.pointer.memberId,
|
|
32881
32894
|
typeInfo: toWireType(storageType, this.project),
|
|
32882
32895
|
writability
|
|
32883
32896
|
},
|
|
32884
|
-
|
|
32885
|
-
|
|
32897
|
+
storageType,
|
|
32898
|
+
scope,
|
|
32899
|
+
pos
|
|
32900
|
+
);
|
|
32886
32901
|
}
|
|
32887
32902
|
/**
|
|
32888
32903
|
* P106. A getter subscription lives only in the running client's memory:
|
|
32889
32904
|
* nothing persists it, so construction replay would silently drop one.
|
|
32890
32905
|
*/
|
|
32891
|
-
|
|
32906
|
+
resolveGetterChangeListener(getterRead, observed, pointer, member, scope, pos, edit) {
|
|
32892
32907
|
if (pointer.dispatch === "base") {
|
|
32893
32908
|
throw new CompileError(
|
|
32894
32909
|
`OnChanged can't observe a base getter. Subscribe to '${member.name}' on the instance.`,
|
|
32895
|
-
|
|
32910
|
+
pos
|
|
32896
32911
|
);
|
|
32897
32912
|
}
|
|
32898
32913
|
if (pointer.receiver.kind !== "instance") {
|
|
32899
32914
|
throw new CompileError(
|
|
32900
32915
|
`OnChanged requires an instance receiver for getter '${member.name}'.`,
|
|
32901
|
-
|
|
32916
|
+
pos
|
|
32902
32917
|
);
|
|
32903
32918
|
}
|
|
32904
32919
|
const kind = this.context.kind;
|
|
32905
32920
|
if (isNeoScriptConstructionDocumentKind(kind) || kind === "migration") {
|
|
32906
32921
|
throw new CompileError(
|
|
32907
|
-
`A getter subscription lives only for the play session, so ${dialogueBodyKindLabel(kind)} can't make one. Subscribe from a function.`,
|
|
32908
|
-
|
|
32909
|
-
);
|
|
32910
|
-
}
|
|
32911
|
-
if (!statement.value) {
|
|
32912
|
-
throw new CompileError(
|
|
32913
|
-
"OnChanged subscription requires a handler member.",
|
|
32914
|
-
statement.pos
|
|
32922
|
+
edit.kind === "clearChangeListeners" /* ClearChangeListeners */ ? `A getter subscription lives only for the play session, so ${dialogueBodyKindLabel(kind)} can't clear one. Clear from a function.` : `A getter subscription lives only for the play session, so ${dialogueBodyKindLabel(kind)} can't make one. Subscribe from a function.`,
|
|
32923
|
+
pos
|
|
32915
32924
|
);
|
|
32916
32925
|
}
|
|
32917
32926
|
const getterType = this.assignmentStorageType(
|
|
@@ -32920,23 +32929,36 @@ var init_strict_resolver = __esm({
|
|
|
32920
32929
|
null,
|
|
32921
32930
|
scope
|
|
32922
32931
|
);
|
|
32923
|
-
|
|
32924
|
-
|
|
32925
|
-
{
|
|
32926
|
-
scope,
|
|
32927
|
-
true,
|
|
32928
|
-
true
|
|
32929
|
-
);
|
|
32930
|
-
return {
|
|
32931
|
-
type: statement.op === "+=" ? "addChangeListener" /* AddChangeListener */ : "removeChangeListener" /* RemoveChangeListener */,
|
|
32932
|
-
target: {
|
|
32932
|
+
return this.changeListenerInstruction(
|
|
32933
|
+
edit,
|
|
32934
|
+
{
|
|
32933
32935
|
owner: pointer.receiver.pointer,
|
|
32934
32936
|
memberId: member.canonicalMemberId ?? pointer.memberId,
|
|
32935
32937
|
typeInfo: toWireType(getterType, this.project),
|
|
32936
32938
|
writability: "session" /* Session */
|
|
32937
32939
|
},
|
|
32938
|
-
|
|
32939
|
-
|
|
32940
|
+
getterType,
|
|
32941
|
+
scope,
|
|
32942
|
+
pos
|
|
32943
|
+
);
|
|
32944
|
+
}
|
|
32945
|
+
changeListenerInstruction(edit, target, observedType, scope, pos) {
|
|
32946
|
+
if (edit.kind === "clearChangeListeners" /* ClearChangeListeners */)
|
|
32947
|
+
return { type: edit.kind, target };
|
|
32948
|
+
if (!edit.handler) {
|
|
32949
|
+
throw new CompileError(
|
|
32950
|
+
"OnChanged subscription requires a handler member.",
|
|
32951
|
+
pos
|
|
32952
|
+
);
|
|
32953
|
+
}
|
|
32954
|
+
const listener = this.resolveActionListener(
|
|
32955
|
+
edit.handler,
|
|
32956
|
+
{ kind: "action", parameterTypes: [observedType] },
|
|
32957
|
+
scope,
|
|
32958
|
+
true,
|
|
32959
|
+
true
|
|
32960
|
+
);
|
|
32961
|
+
return { type: edit.kind, target, listener };
|
|
32940
32962
|
}
|
|
32941
32963
|
/**
|
|
32942
32964
|
* P62 §3.2. `+=` / `-=` on an NSAction member are subscription operators,
|
|
@@ -35054,6 +35076,8 @@ var init_strict_resolver = __esm({
|
|
|
35054
35076
|
entryUserData
|
|
35055
35077
|
};
|
|
35056
35078
|
}
|
|
35079
|
+
if (name === "OnChanged")
|
|
35080
|
+
throw new CompileError(ON_CHANGED_OPERATIONS_ERROR, pos);
|
|
35057
35081
|
throw new CompileError(
|
|
35058
35082
|
`Cannot read member '${name}' on ${this.describe(receiver.type)}`,
|
|
35059
35083
|
pos
|
|
@@ -51773,7 +51797,7 @@ var init_registry = __esm({
|
|
|
51773
51797
|
"../packages/manifest/schema-contract/registry.mjs"() {
|
|
51774
51798
|
"use strict";
|
|
51775
51799
|
PROJECT_SCHEMA_CONTRACT = Object.freeze({
|
|
51776
|
-
cliVersion: "0.
|
|
51800
|
+
cliVersion: "0.75.0",
|
|
51777
51801
|
projectFileUploadBatchSize: 32,
|
|
51778
51802
|
documentRecords: {
|
|
51779
51803
|
member: {
|
|
@@ -78978,6 +79002,9 @@ function listenerWritesByOwner(state) {
|
|
|
78978
79002
|
}
|
|
78979
79003
|
return index.owners;
|
|
78980
79004
|
}
|
|
79005
|
+
function changeListenerWriteEdit(write) {
|
|
79006
|
+
return write.operation === "clear" ? { kind: "replace", targets: [] } : { kind: write.operation, target: write.listener };
|
|
79007
|
+
}
|
|
78981
79008
|
var indexes, empty;
|
|
78982
79009
|
var init_listener_write_index = __esm({
|
|
78983
79010
|
"../src/runtime/listener-write-index.ts"() {
|
|
@@ -81888,33 +81915,41 @@ function finalizeConstructorAllocations(ctx, returnValue, ownsInvocationState, r
|
|
|
81888
81915
|
const listenerEdges = /* @__PURE__ */ new Map();
|
|
81889
81916
|
for (const write of state.writes) {
|
|
81890
81917
|
if (!retainListenerReceivers || write.kind !== "changeListener") continue;
|
|
81918
|
+
const slotKey = JSON.stringify([write.ownerId, write.memberId]);
|
|
81919
|
+
if (write.operation === "clear") {
|
|
81920
|
+
listenerEdges.delete(slotKey);
|
|
81921
|
+
continue;
|
|
81922
|
+
}
|
|
81891
81923
|
const key = JSON.stringify([
|
|
81892
|
-
write.ownerId,
|
|
81893
|
-
write.memberId,
|
|
81894
81924
|
write.listener.memberId,
|
|
81895
81925
|
write.listener.valueId
|
|
81896
81926
|
]);
|
|
81897
|
-
|
|
81898
|
-
|
|
81927
|
+
let edges = listenerEdges.get(slotKey);
|
|
81928
|
+
if (write.operation === "remove") edges?.delete(key);
|
|
81929
|
+
else {
|
|
81930
|
+
if (edges === void 0) listenerEdges.set(slotKey, edges = /* @__PURE__ */ new Map());
|
|
81931
|
+
edges.set(key, write);
|
|
81932
|
+
}
|
|
81899
81933
|
}
|
|
81900
81934
|
const externalListenerReceivers = /* @__PURE__ */ new Set();
|
|
81901
|
-
for (const
|
|
81902
|
-
|
|
81903
|
-
|
|
81904
|
-
|
|
81905
|
-
|
|
81906
|
-
|
|
81907
|
-
if (
|
|
81908
|
-
|
|
81909
|
-
|
|
81910
|
-
|
|
81911
|
-
|
|
81912
|
-
dependencies
|
|
81913
|
-
|
|
81935
|
+
for (const edges of listenerEdges.values())
|
|
81936
|
+
for (const write of edges.values()) {
|
|
81937
|
+
if (write.listener.valueId === null) continue;
|
|
81938
|
+
const receiverGroup = groupByRowId.get(write.listener.valueId);
|
|
81939
|
+
if (receiverGroup === void 0) continue;
|
|
81940
|
+
const ownerGroup = groupByRowId.get(write.ownerId);
|
|
81941
|
+
if (ownerGroup === void 0) {
|
|
81942
|
+
if (rowForCleanup(write.ownerId) !== null)
|
|
81943
|
+
externalListenerReceivers.add(receiverGroup);
|
|
81944
|
+
} else {
|
|
81945
|
+
let dependencies = state.constructorGroupDependencies.get(ownerGroup);
|
|
81946
|
+
if (dependencies === void 0) {
|
|
81947
|
+
dependencies = /* @__PURE__ */ new Set();
|
|
81948
|
+
state.constructorGroupDependencies.set(ownerGroup, dependencies);
|
|
81949
|
+
}
|
|
81950
|
+
dependencies.add(receiverGroup);
|
|
81914
81951
|
}
|
|
81915
|
-
dependencies.add(receiverGroup);
|
|
81916
81952
|
}
|
|
81917
|
-
}
|
|
81918
81953
|
const markConstructorGroupEscaped = (rootId) => {
|
|
81919
81954
|
if (escapedRoots.has(rootId)) return;
|
|
81920
81955
|
escapedRoots.add(rootId);
|
|
@@ -82111,7 +82146,7 @@ function finalizeConstructorAllocations(ctx, returnValue, ownsInvocationState, r
|
|
|
82111
82146
|
const survivingWrites = state.writes.filter((write) => {
|
|
82112
82147
|
if (write.kind === "changeListener") {
|
|
82113
82148
|
if (collectedIds.has(write.ownerId)) return false;
|
|
82114
|
-
return write.listener.valueId === null || !collectedIds.has(write.listener.valueId);
|
|
82149
|
+
return write.operation === "clear" || write.listener.valueId === null || !collectedIds.has(write.listener.valueId);
|
|
82115
82150
|
}
|
|
82116
82151
|
const destination = write.resolvedDestination;
|
|
82117
82152
|
const destinationId = destination !== void 0 && "containerValueId" in destination ? destination.containerValueId : void 0;
|
|
@@ -82752,7 +82787,8 @@ function evalInstructions(instructions, scope, ctx, options) {
|
|
|
82752
82787
|
break;
|
|
82753
82788
|
}
|
|
82754
82789
|
case "addChangeListener" /* addChangeListener */:
|
|
82755
|
-
case "removeChangeListener" /* removeChangeListener */:
|
|
82790
|
+
case "removeChangeListener" /* removeChangeListener */:
|
|
82791
|
+
case "clearChangeListeners" /* clearChangeListeners */: {
|
|
82756
82792
|
evalChangeListenerInstruction(ins, scope, ctx, options);
|
|
82757
82793
|
break;
|
|
82758
82794
|
}
|
|
@@ -83182,6 +83218,18 @@ function evalChangeListenerInstruction(ins, scope, ctx, options) {
|
|
|
83182
83218
|
throw new NSGetterRuntimeError(
|
|
83183
83219
|
`OnChanged member '${ins.target.memberId}' is not an observable member or getter of owner '${owner.id}'.`
|
|
83184
83220
|
);
|
|
83221
|
+
if (ins.type === "clearChangeListeners" /* clearChangeListeners */) {
|
|
83222
|
+
if (getter !== null) clearGetterSubscription(getter, ctx);
|
|
83223
|
+
else
|
|
83224
|
+
options.writes.push({
|
|
83225
|
+
kind: "changeListener",
|
|
83226
|
+
target: ins.target,
|
|
83227
|
+
ownerId: owner.id,
|
|
83228
|
+
memberId: slot.memberId,
|
|
83229
|
+
operation: "clear"
|
|
83230
|
+
});
|
|
83231
|
+
return;
|
|
83232
|
+
}
|
|
83185
83233
|
const listener = evalPointer(ins.listener, scope, ctx);
|
|
83186
83234
|
if (!isMemberDelegateTarget(listener))
|
|
83187
83235
|
throw new NSGetterRuntimeError(
|
|
@@ -83238,6 +83286,17 @@ function editGetterSubscription(slot, target, add, ctx) {
|
|
|
83238
83286
|
}
|
|
83239
83287
|
subscription.targets.push(target);
|
|
83240
83288
|
}
|
|
83289
|
+
function clearGetterSubscription(slot, ctx) {
|
|
83290
|
+
if ((ctx.__executionState?.constructionStack.length ?? 0) > 0)
|
|
83291
|
+
throw new NSGetterRuntimeError(
|
|
83292
|
+
"A getter subscription lives only for the play session, so a constructor can't clear one. Clear from a function."
|
|
83293
|
+
);
|
|
83294
|
+
const subscriptions = ctx.__getterSubscriptions;
|
|
83295
|
+
const subscription = subscriptions?.get(slot.owner.id, slot.memberId);
|
|
83296
|
+
if (subscriptions === void 0 || subscription === void 0) return;
|
|
83297
|
+
subscription.targets.length = 0;
|
|
83298
|
+
subscriptions.drop(subscription);
|
|
83299
|
+
}
|
|
83241
83300
|
function dispatchGetterSubscriptions(ctx) {
|
|
83242
83301
|
const subscriptions = ctx.__getterSubscriptions;
|
|
83243
83302
|
if (subscriptions === void 0) return;
|
|
@@ -91540,7 +91599,7 @@ function cloneClassValueGraph(receiver, expectedClassId, ctx) {
|
|
|
91540
91599
|
const edit = editChangeListenerSet(
|
|
91541
91600
|
entry?.[write.memberId],
|
|
91542
91601
|
[],
|
|
91543
|
-
|
|
91602
|
+
changeListenerWriteEdit(write),
|
|
91544
91603
|
{ inheritedComplete: false }
|
|
91545
91604
|
);
|
|
91546
91605
|
if (!edit.changed) continue;
|
|
@@ -109013,10 +109072,11 @@ function migrationMemberListenerRows(document, writes, copied) {
|
|
|
109013
109072
|
const map = root.changeListeners;
|
|
109014
109073
|
const entry = map[write.ownerId] ?? {};
|
|
109015
109074
|
const inherited = reader.listenerDefaultsForOwner(write.ownerId)?.[write.memberId] ?? [];
|
|
109016
|
-
const edit = editChangeListenerSet(
|
|
109017
|
-
|
|
109018
|
-
|
|
109019
|
-
|
|
109075
|
+
const edit = editChangeListenerSet(
|
|
109076
|
+
entry[write.memberId],
|
|
109077
|
+
inherited,
|
|
109078
|
+
changeListenerWriteEdit(write)
|
|
109079
|
+
);
|
|
109020
109080
|
if (!edit.changed) continue;
|
|
109021
109081
|
if (edit.targets === void 0) delete entry[write.memberId];
|
|
109022
109082
|
else entry[write.memberId] = edit.targets;
|
|
@@ -109049,6 +109109,7 @@ var init_project_migration_member_listeners = __esm({
|
|
|
109049
109109
|
init_members();
|
|
109050
109110
|
init_project_root_members();
|
|
109051
109111
|
init_member_change_listeners();
|
|
109112
|
+
init_listener_write_index();
|
|
109052
109113
|
init_authored_listener_endpoints();
|
|
109053
109114
|
}
|
|
109054
109115
|
});
|
|
@@ -172172,7 +172233,7 @@ var init_project_unity_export_types = __esm({
|
|
|
172172
172233
|
"../src/models/exports/project-unity-export-types.ts"() {
|
|
172173
172234
|
"use strict";
|
|
172174
172235
|
init_unity_codegen_revision();
|
|
172175
|
-
PROJECT_UNITY_EXPORT_SCHEMA_VERSION =
|
|
172236
|
+
PROJECT_UNITY_EXPORT_SCHEMA_VERSION = 41;
|
|
172176
172237
|
}
|
|
172177
172238
|
});
|
|
172178
172239
|
|
|
@@ -189159,7 +189220,7 @@ var init_member_change_listener_runtime = __esm({
|
|
|
189159
189220
|
for (const ownerId of owners)
|
|
189160
189221
|
for (const write of writes.get(ownerId) ?? []) {
|
|
189161
189222
|
const slot = this.slot(write.ownerId, write.memberId);
|
|
189162
|
-
if (slot === null || this.targetTier(slot, write.listener)?.tier !== "save")
|
|
189223
|
+
if (slot === null || write.operation !== "clear" && this.targetTier(slot, write.listener)?.tier !== "save")
|
|
189163
189224
|
continue;
|
|
189164
189225
|
const entry = map[write.ownerId] ?? {};
|
|
189165
189226
|
const inherited = this.baseline(slot, "save");
|
|
@@ -189173,7 +189234,7 @@ var init_member_change_listener_runtime = __esm({
|
|
|
189173
189234
|
const edited = editChangeListenerSet(
|
|
189174
189235
|
durable,
|
|
189175
189236
|
inherited.targets,
|
|
189176
|
-
|
|
189237
|
+
changeListenerWriteEdit(write),
|
|
189177
189238
|
{ inheritedComplete: inherited.complete }
|
|
189178
189239
|
);
|
|
189179
189240
|
if (!edited.changed) continue;
|
|
@@ -189265,41 +189326,48 @@ var init_member_change_listener_runtime = __esm({
|
|
|
189265
189326
|
throw new Error(
|
|
189266
189327
|
`OnChanged slot '${write.ownerId}/${write.memberId}' is not writable in this runtime.`
|
|
189267
189328
|
);
|
|
189268
|
-
|
|
189329
|
+
const targets = [];
|
|
189269
189330
|
if (write.operation === "reset")
|
|
189270
|
-
|
|
189271
|
-
else {
|
|
189331
|
+
targets.push({ tier: write.tier, edit: { kind: "reset" } });
|
|
189332
|
+
else if (write.operation === "clear") {
|
|
189333
|
+
const edit = changeListenerWriteEdit(write);
|
|
189334
|
+
if (slot.storage !== 3 /* Session */)
|
|
189335
|
+
targets.push({ tier: "save", edit });
|
|
189336
|
+
targets.push({ tier: "session", edit });
|
|
189337
|
+
} else {
|
|
189272
189338
|
const resolved = this.targetTier(slot, write.listener);
|
|
189273
189339
|
if (resolved === null)
|
|
189274
189340
|
throw new Error(
|
|
189275
189341
|
`OnChanged handler '${write.listener.memberId}' is incompatible with '${write.ownerId}/${write.memberId}'.`
|
|
189276
189342
|
);
|
|
189277
189343
|
const inSession = current("session", slot.rootId, slot.owner.id)?.[slot.memberId]?.some(
|
|
189278
|
-
(
|
|
189344
|
+
(existing) => nsActionListenerIdentity(existing) === nsActionListenerIdentity(resolved.target)
|
|
189279
189345
|
);
|
|
189280
|
-
|
|
189346
|
+
targets.push({
|
|
189281
189347
|
tier: inSession ? "session" : resolved.tier,
|
|
189282
189348
|
edit: { kind: write.operation, target: resolved.target }
|
|
189283
|
-
};
|
|
189349
|
+
});
|
|
189350
|
+
}
|
|
189351
|
+
for (const target of targets) {
|
|
189352
|
+
this.host.assertWritable(slot, target.tier);
|
|
189353
|
+
const existing = current(target.tier, slot.rootId, slot.owner.id);
|
|
189354
|
+
const explicit = existing !== void 0 && Object.hasOwn(existing, slot.memberId) ? existing[slot.memberId] : void 0;
|
|
189355
|
+
const baseline = this.baseline(
|
|
189356
|
+
slot,
|
|
189357
|
+
target.tier,
|
|
189358
|
+
target.tier !== "save" || !adopting.has(slot.owner.id)
|
|
189359
|
+
);
|
|
189360
|
+
const result = editChangeListenerSet(
|
|
189361
|
+
explicit,
|
|
189362
|
+
baseline.targets,
|
|
189363
|
+
target.edit,
|
|
189364
|
+
{ inheritedComplete: baseline.complete }
|
|
189365
|
+
);
|
|
189366
|
+
if (!result.changed) continue;
|
|
189367
|
+
const entry = editOwner(target.tier, slot.rootId, slot.owner.id);
|
|
189368
|
+
if (result.targets === void 0) delete entry[slot.memberId];
|
|
189369
|
+
else setOwnField(entry, slot.memberId, result.targets);
|
|
189284
189370
|
}
|
|
189285
|
-
this.host.assertWritable(slot, target.tier);
|
|
189286
|
-
const existing = current(target.tier, slot.rootId, slot.owner.id);
|
|
189287
|
-
const explicit = existing !== void 0 && Object.hasOwn(existing, slot.memberId) ? existing[slot.memberId] : void 0;
|
|
189288
|
-
const baseline = this.baseline(
|
|
189289
|
-
slot,
|
|
189290
|
-
target.tier,
|
|
189291
|
-
target.tier !== "save" || !adopting.has(slot.owner.id)
|
|
189292
|
-
);
|
|
189293
|
-
const result = editChangeListenerSet(
|
|
189294
|
-
explicit,
|
|
189295
|
-
baseline.targets,
|
|
189296
|
-
target.edit,
|
|
189297
|
-
{ inheritedComplete: baseline.complete }
|
|
189298
|
-
);
|
|
189299
|
-
if (!result.changed) continue;
|
|
189300
|
-
const entry = editOwner(target.tier, slot.rootId, slot.owner.id);
|
|
189301
|
-
if (result.targets === void 0) delete entry[slot.memberId];
|
|
189302
|
-
else setOwnField(entry, slot.memberId, result.targets);
|
|
189303
189371
|
}
|
|
189304
189372
|
for (const tier of ["save", "session"]) {
|
|
189305
189373
|
for (const [rootId, changed] of pending[tier]) {
|
|
@@ -190173,7 +190241,7 @@ var init_dialogue_dryrun_listeners = __esm({
|
|
|
190173
190241
|
this.copied = context.__copiedChangeListeners ?? this.copied;
|
|
190174
190242
|
this.runtime.retireOwners(context.__removedValueIds ?? []);
|
|
190175
190243
|
const listenerWrites = evaluatedWrites(result).filter(
|
|
190176
|
-
(write) => write.kind === "changeListener" && !context.__removedValueIds?.has(write.ownerId) && (write.listener.valueId === null || !context.__removedValueIds?.has(write.listener.valueId))
|
|
190244
|
+
(write) => write.kind === "changeListener" && !context.__removedValueIds?.has(write.ownerId) && (write.operation === "clear" || write.listener.valueId === null || !context.__removedValueIds?.has(write.listener.valueId))
|
|
190177
190245
|
);
|
|
190178
190246
|
const adopted = new Set(
|
|
190179
190247
|
(result.createdSessionValues ?? []).map((row) => row.id)
|
|
@@ -191907,7 +191975,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
|
|
|
191907
191975
|
async function main() {
|
|
191908
191976
|
const args = parseArgs(process.argv.slice(2));
|
|
191909
191977
|
if (args.command === "--version") {
|
|
191910
|
-
console.log("0.
|
|
191978
|
+
console.log("0.75.0");
|
|
191911
191979
|
return;
|
|
191912
191980
|
}
|
|
191913
191981
|
if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
|
package/dist/test-worker.mjs
CHANGED
|
@@ -3629,24 +3629,30 @@ function isNSInstructionTry(value) {
|
|
|
3629
3629
|
}
|
|
3630
3630
|
return true;
|
|
3631
3631
|
}
|
|
3632
|
-
function
|
|
3633
|
-
const
|
|
3634
|
-
if (instruction?.type !== "addChangeListener" /* addChangeListener */ && instruction?.type !== "removeChangeListener" /* removeChangeListener */)
|
|
3635
|
-
return false;
|
|
3636
|
-
const target = instruction.target;
|
|
3632
|
+
function isNSChangeListenerTarget(value) {
|
|
3633
|
+
const target = value;
|
|
3637
3634
|
if (!target || typeof target.memberId !== "string" || target.memberId.length === 0)
|
|
3638
3635
|
return false;
|
|
3639
3636
|
if (!isNSPointer(target.owner) || !isNSTypeInfo(target.typeInfo))
|
|
3640
3637
|
return false;
|
|
3641
|
-
|
|
3642
|
-
|
|
3638
|
+
return target.writability !== void 0 && Object.values(NSWritability).includes(target.writability);
|
|
3639
|
+
}
|
|
3640
|
+
function isNSInstructionChangeListener(value) {
|
|
3641
|
+
const instruction = value;
|
|
3642
|
+
if (instruction?.type !== "addChangeListener" /* addChangeListener */ && instruction?.type !== "removeChangeListener" /* removeChangeListener */)
|
|
3643
|
+
return false;
|
|
3644
|
+
return isNSChangeListenerTarget(instruction.target) && isNSPointer(instruction.listener);
|
|
3645
|
+
}
|
|
3646
|
+
function isNSInstructionClearChangeListeners(value) {
|
|
3647
|
+
const instruction = value;
|
|
3648
|
+
return instruction?.type === "clearChangeListeners" /* clearChangeListeners */ && isNSChangeListenerTarget(instruction.target);
|
|
3643
3649
|
}
|
|
3644
3650
|
function isNSInstructionDebug(value) {
|
|
3645
3651
|
const v = value;
|
|
3646
3652
|
return v?.type === "debug" /* debug */ && ["log", "warning", "error", "assert"].includes(v.severity ?? "") && isNSPointer(v.message) && isNSTypeInfo(v.messageType) && (v.severity === "assert" ? isNSPointer(v.condition) : v.condition === void 0);
|
|
3647
3653
|
}
|
|
3648
3654
|
function isNSInstruction(value) {
|
|
3649
|
-
return isNSInstructionDebug(value) || isNSInstructionVariable(value) || isNSInstructionIfBranch(value) || isNSInstructionReturn(value) || isNSInstructionThrow(value) || isNSInstructionAssign(value) || isNSInstructionCollectionCall(value) || isNSInstructionFunctionCall(value) || isNSInstructionConditionLoop(value) || isNSInstructionFor(value) || isNSInstructionForEach(value) || isNSInstructionBreak(value) || isNSInstructionContinue(value) || isNSInstructionSwitch(value) || isNSInstructionTry(value) || isNSInstructionChangeListener(value) || isNSInstructionAddActionListener(value) || isNSInstructionRemoveActionListener(value);
|
|
3655
|
+
return isNSInstructionDebug(value) || isNSInstructionVariable(value) || isNSInstructionIfBranch(value) || isNSInstructionReturn(value) || isNSInstructionThrow(value) || isNSInstructionAssign(value) || isNSInstructionCollectionCall(value) || isNSInstructionFunctionCall(value) || isNSInstructionConditionLoop(value) || isNSInstructionFor(value) || isNSInstructionForEach(value) || isNSInstructionBreak(value) || isNSInstructionContinue(value) || isNSInstructionSwitch(value) || isNSInstructionTry(value) || isNSInstructionChangeListener(value) || isNSInstructionClearChangeListeners(value) || isNSInstructionAddActionListener(value) || isNSInstructionRemoveActionListener(value);
|
|
3650
3656
|
}
|
|
3651
3657
|
function isNSInstructions(value) {
|
|
3652
3658
|
return Array.isArray(value) && value.every(isNSInstruction);
|
|
@@ -13204,6 +13210,9 @@ function listenerWritesByOwner(state) {
|
|
|
13204
13210
|
}
|
|
13205
13211
|
return index.owners;
|
|
13206
13212
|
}
|
|
13213
|
+
function changeListenerWriteEdit(write) {
|
|
13214
|
+
return write.operation === "clear" ? { kind: "replace", targets: [] } : { kind: write.operation, target: write.listener };
|
|
13215
|
+
}
|
|
13207
13216
|
|
|
13208
13217
|
// ../src/runtime/neoscript/typed-aggregate-row-references.ts
|
|
13209
13218
|
function visitTypedAggregateRowReferences(value, rawType, visitor) {
|
|
@@ -17032,33 +17041,41 @@ function finalizeConstructorAllocations(ctx, returnValue, ownsInvocationState, r
|
|
|
17032
17041
|
const listenerEdges = /* @__PURE__ */ new Map();
|
|
17033
17042
|
for (const write of state.writes) {
|
|
17034
17043
|
if (!retainListenerReceivers || write.kind !== "changeListener") continue;
|
|
17044
|
+
const slotKey = JSON.stringify([write.ownerId, write.memberId]);
|
|
17045
|
+
if (write.operation === "clear") {
|
|
17046
|
+
listenerEdges.delete(slotKey);
|
|
17047
|
+
continue;
|
|
17048
|
+
}
|
|
17035
17049
|
const key = JSON.stringify([
|
|
17036
|
-
write.ownerId,
|
|
17037
|
-
write.memberId,
|
|
17038
17050
|
write.listener.memberId,
|
|
17039
17051
|
write.listener.valueId
|
|
17040
17052
|
]);
|
|
17041
|
-
|
|
17042
|
-
|
|
17053
|
+
let edges = listenerEdges.get(slotKey);
|
|
17054
|
+
if (write.operation === "remove") edges?.delete(key);
|
|
17055
|
+
else {
|
|
17056
|
+
if (edges === void 0) listenerEdges.set(slotKey, edges = /* @__PURE__ */ new Map());
|
|
17057
|
+
edges.set(key, write);
|
|
17058
|
+
}
|
|
17043
17059
|
}
|
|
17044
17060
|
const externalListenerReceivers = /* @__PURE__ */ new Set();
|
|
17045
|
-
for (const
|
|
17046
|
-
|
|
17047
|
-
|
|
17048
|
-
|
|
17049
|
-
|
|
17050
|
-
|
|
17051
|
-
if (
|
|
17052
|
-
|
|
17053
|
-
|
|
17054
|
-
|
|
17055
|
-
|
|
17056
|
-
dependencies
|
|
17057
|
-
|
|
17061
|
+
for (const edges of listenerEdges.values())
|
|
17062
|
+
for (const write of edges.values()) {
|
|
17063
|
+
if (write.listener.valueId === null) continue;
|
|
17064
|
+
const receiverGroup = groupByRowId.get(write.listener.valueId);
|
|
17065
|
+
if (receiverGroup === void 0) continue;
|
|
17066
|
+
const ownerGroup = groupByRowId.get(write.ownerId);
|
|
17067
|
+
if (ownerGroup === void 0) {
|
|
17068
|
+
if (rowForCleanup(write.ownerId) !== null)
|
|
17069
|
+
externalListenerReceivers.add(receiverGroup);
|
|
17070
|
+
} else {
|
|
17071
|
+
let dependencies = state.constructorGroupDependencies.get(ownerGroup);
|
|
17072
|
+
if (dependencies === void 0) {
|
|
17073
|
+
dependencies = /* @__PURE__ */ new Set();
|
|
17074
|
+
state.constructorGroupDependencies.set(ownerGroup, dependencies);
|
|
17075
|
+
}
|
|
17076
|
+
dependencies.add(receiverGroup);
|
|
17058
17077
|
}
|
|
17059
|
-
dependencies.add(receiverGroup);
|
|
17060
17078
|
}
|
|
17061
|
-
}
|
|
17062
17079
|
const markConstructorGroupEscaped = (rootId) => {
|
|
17063
17080
|
if (escapedRoots.has(rootId)) return;
|
|
17064
17081
|
escapedRoots.add(rootId);
|
|
@@ -17255,7 +17272,7 @@ function finalizeConstructorAllocations(ctx, returnValue, ownsInvocationState, r
|
|
|
17255
17272
|
const survivingWrites = state.writes.filter((write) => {
|
|
17256
17273
|
if (write.kind === "changeListener") {
|
|
17257
17274
|
if (collectedIds.has(write.ownerId)) return false;
|
|
17258
|
-
return write.listener.valueId === null || !collectedIds.has(write.listener.valueId);
|
|
17275
|
+
return write.operation === "clear" || write.listener.valueId === null || !collectedIds.has(write.listener.valueId);
|
|
17259
17276
|
}
|
|
17260
17277
|
const destination = write.resolvedDestination;
|
|
17261
17278
|
const destinationId = destination !== void 0 && "containerValueId" in destination ? destination.containerValueId : void 0;
|
|
@@ -18009,7 +18026,8 @@ function evalInstructions(instructions, scope, ctx, options) {
|
|
|
18009
18026
|
break;
|
|
18010
18027
|
}
|
|
18011
18028
|
case "addChangeListener" /* addChangeListener */:
|
|
18012
|
-
case "removeChangeListener" /* removeChangeListener */:
|
|
18029
|
+
case "removeChangeListener" /* removeChangeListener */:
|
|
18030
|
+
case "clearChangeListeners" /* clearChangeListeners */: {
|
|
18013
18031
|
evalChangeListenerInstruction(ins, scope, ctx, options);
|
|
18014
18032
|
break;
|
|
18015
18033
|
}
|
|
@@ -18430,6 +18448,18 @@ function evalChangeListenerInstruction(ins, scope, ctx, options) {
|
|
|
18430
18448
|
throw new NSGetterRuntimeError(
|
|
18431
18449
|
`OnChanged member '${ins.target.memberId}' is not an observable member or getter of owner '${owner.id}'.`
|
|
18432
18450
|
);
|
|
18451
|
+
if (ins.type === "clearChangeListeners" /* clearChangeListeners */) {
|
|
18452
|
+
if (getter !== null) clearGetterSubscription(getter, ctx);
|
|
18453
|
+
else
|
|
18454
|
+
options.writes.push({
|
|
18455
|
+
kind: "changeListener",
|
|
18456
|
+
target: ins.target,
|
|
18457
|
+
ownerId: owner.id,
|
|
18458
|
+
memberId: slot.memberId,
|
|
18459
|
+
operation: "clear"
|
|
18460
|
+
});
|
|
18461
|
+
return;
|
|
18462
|
+
}
|
|
18433
18463
|
const listener = evalPointer(ins.listener, scope, ctx);
|
|
18434
18464
|
if (!isMemberDelegateTarget(listener))
|
|
18435
18465
|
throw new NSGetterRuntimeError(
|
|
@@ -18486,6 +18516,17 @@ function editGetterSubscription(slot, target, add, ctx) {
|
|
|
18486
18516
|
}
|
|
18487
18517
|
subscription.targets.push(target);
|
|
18488
18518
|
}
|
|
18519
|
+
function clearGetterSubscription(slot, ctx) {
|
|
18520
|
+
if ((ctx.__executionState?.constructionStack.length ?? 0) > 0)
|
|
18521
|
+
throw new NSGetterRuntimeError(
|
|
18522
|
+
"A getter subscription lives only for the play session, so a constructor can't clear one. Clear from a function."
|
|
18523
|
+
);
|
|
18524
|
+
const subscriptions = ctx.__getterSubscriptions;
|
|
18525
|
+
const subscription = subscriptions?.get(slot.owner.id, slot.memberId);
|
|
18526
|
+
if (subscriptions === void 0 || subscription === void 0) return;
|
|
18527
|
+
subscription.targets.length = 0;
|
|
18528
|
+
subscriptions.drop(subscription);
|
|
18529
|
+
}
|
|
18489
18530
|
function dispatchGetterSubscriptions(ctx) {
|
|
18490
18531
|
const subscriptions = ctx.__getterSubscriptions;
|
|
18491
18532
|
if (subscriptions === void 0) return;
|
|
@@ -26802,7 +26843,7 @@ function cloneClassValueGraph(receiver, expectedClassId, ctx) {
|
|
|
26802
26843
|
const edit = editChangeListenerSet(
|
|
26803
26844
|
entry?.[write.memberId],
|
|
26804
26845
|
[],
|
|
26805
|
-
|
|
26846
|
+
changeListenerWriteEdit(write),
|
|
26806
26847
|
{ inheritedComplete: false }
|
|
26807
26848
|
);
|
|
26808
26849
|
if (!edit.changed) continue;
|
|
@@ -34329,6 +34370,7 @@ var WRITE_TARGET_DESCRIPTIONS = {
|
|
|
34329
34370
|
projectValue: "a stored project value",
|
|
34330
34371
|
unknown: "something other than `this` or a local"
|
|
34331
34372
|
};
|
|
34373
|
+
var ON_CHANGED_OPERATIONS_ERROR = "OnChanged supports only '+=', '-=', and Clear().";
|
|
34332
34374
|
var FALLTHROUGH_LABELS = {
|
|
34333
34375
|
function: "NSFunction",
|
|
34334
34376
|
getter: "getter",
|
|
@@ -35671,6 +35713,12 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
|
|
|
35671
35713
|
this.resolveExpression(statement.expr, scope);
|
|
35672
35714
|
const debug = this.resolveDebugStatement(statement.expr, scope);
|
|
35673
35715
|
if (debug) return debug;
|
|
35716
|
+
const listenerClear = this.resolveChangeListenerClear(
|
|
35717
|
+
statement.expr,
|
|
35718
|
+
scope,
|
|
35719
|
+
statement.pos
|
|
35720
|
+
);
|
|
35721
|
+
if (listenerClear) return listenerClear;
|
|
35674
35722
|
const actionClear = this.resolveActionClear(
|
|
35675
35723
|
statement.expr,
|
|
35676
35724
|
scope,
|
|
@@ -36031,8 +36079,20 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
|
|
|
36031
36079
|
const observed = this.resolveExpression(statement.target.receiver, scope);
|
|
36032
36080
|
const observedType = neoScriptMemberLookupType(observed.type);
|
|
36033
36081
|
const declared = observedType.kind === "named" && this.project.typeById.get(observedType.typeId)?.members.some((member) => member.name === "OnChanged");
|
|
36034
|
-
if (!declared)
|
|
36035
|
-
|
|
36082
|
+
if (!declared) {
|
|
36083
|
+
if (statement.op !== "+=" && statement.op !== "-=")
|
|
36084
|
+
throw new CompileError(ON_CHANGED_OPERATIONS_ERROR, statement.pos);
|
|
36085
|
+
return this.resolveChangeListener(
|
|
36086
|
+
statement.target,
|
|
36087
|
+
observed,
|
|
36088
|
+
scope,
|
|
36089
|
+
statement.pos,
|
|
36090
|
+
{
|
|
36091
|
+
kind: statement.op === "+=" ? "addChangeListener" /* AddChangeListener */ : "removeChangeListener" /* RemoveChangeListener */,
|
|
36092
|
+
handler: statement.value
|
|
36093
|
+
}
|
|
36094
|
+
);
|
|
36095
|
+
}
|
|
36036
36096
|
}
|
|
36037
36097
|
const local = statement.target.kind === "ident" ? scope.lookup(statement.target.name) : null;
|
|
36038
36098
|
if (local?.readonlyBinding) {
|
|
@@ -36216,158 +36276,142 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
|
|
|
36216
36276
|
pos
|
|
36217
36277
|
);
|
|
36218
36278
|
}
|
|
36219
|
-
|
|
36220
|
-
|
|
36221
|
-
|
|
36222
|
-
|
|
36223
|
-
|
|
36224
|
-
|
|
36225
|
-
|
|
36226
|
-
if (
|
|
36279
|
+
/** `X.M.OnChanged.Clear()` empties M's listener set on X (P70, P106). */
|
|
36280
|
+
resolveChangeListenerClear(expression, scope, pos) {
|
|
36281
|
+
if (expression.kind !== "call" || expression.callee.kind !== "member" || expression.callee.name !== "Clear" || expression.callee.receiver.kind !== "member" || expression.callee.receiver.name !== "OnChanged")
|
|
36282
|
+
return null;
|
|
36283
|
+
const onChanged = expression.callee.receiver;
|
|
36284
|
+
const observed = this.resolveExpression(onChanged.receiver, scope);
|
|
36285
|
+
const observedType = neoScriptMemberLookupType(observed.type);
|
|
36286
|
+
if (observedType.kind === "named" && this.project.typeById.get(observedType.typeId)?.members.some((member) => member.name === "OnChanged"))
|
|
36287
|
+
return null;
|
|
36288
|
+
if (expression.callee.optional)
|
|
36227
36289
|
throw new CompileError(
|
|
36228
|
-
"OnChanged
|
|
36229
|
-
|
|
36290
|
+
"OnChanged is never null, so optional-chained Clear is not supported.",
|
|
36291
|
+
pos
|
|
36230
36292
|
);
|
|
36231
|
-
|
|
36232
|
-
|
|
36293
|
+
requireArgCount("Clear", expression.args, 0, pos);
|
|
36294
|
+
return this.resolveChangeListener(onChanged, observed, scope, pos, {
|
|
36295
|
+
kind: "clearChangeListeners" /* ClearChangeListeners */
|
|
36296
|
+
});
|
|
36297
|
+
}
|
|
36298
|
+
resolveChangeListener(onChanged, observed, scope, pos, edit) {
|
|
36299
|
+
if (onChanged.optional) {
|
|
36233
36300
|
throw new CompileError(
|
|
36234
36301
|
"OnChanged cannot use an optional subscription target.",
|
|
36235
|
-
|
|
36302
|
+
pos
|
|
36236
36303
|
);
|
|
36237
36304
|
}
|
|
36238
36305
|
if (observed.optionalChainType !== void 0) {
|
|
36239
36306
|
throw new CompileError(
|
|
36240
36307
|
"OnChanged cannot subscribe through optional chaining; narrow the receiver first.",
|
|
36241
|
-
|
|
36308
|
+
pos
|
|
36242
36309
|
);
|
|
36243
36310
|
}
|
|
36244
36311
|
const member = observed.symbol;
|
|
36245
36312
|
if (!member)
|
|
36246
36313
|
throw new CompileError(
|
|
36247
36314
|
"OnChanged requires a declared member, not a local variable.",
|
|
36248
|
-
|
|
36315
|
+
pos
|
|
36249
36316
|
);
|
|
36250
36317
|
if (observed.pointer.type === "staticMember" /* StaticMember */ || observed.pointer.type === "callGetter" /* CallGetter */ && observed.pointer.receiver.kind === "static") {
|
|
36251
|
-
const access =
|
|
36318
|
+
const access = onChanged.receiver;
|
|
36252
36319
|
const name = access.kind === "member" && access.receiver.kind === "ident" ? `${access.receiver.name}.${member.name}` : member.name;
|
|
36253
36320
|
throw new CompileError(
|
|
36254
36321
|
`OnChanged requires an instance member. '${name}' is static.`,
|
|
36255
|
-
|
|
36322
|
+
pos
|
|
36256
36323
|
);
|
|
36257
36324
|
}
|
|
36258
36325
|
if (observed.pointer.type === "callGetter" /* CallGetter */)
|
|
36259
|
-
return this.
|
|
36260
|
-
|
|
36261
|
-
statement.target.receiver,
|
|
36326
|
+
return this.resolveGetterChangeListener(
|
|
36327
|
+
onChanged.receiver,
|
|
36262
36328
|
observed,
|
|
36263
36329
|
observed.pointer,
|
|
36264
36330
|
member,
|
|
36265
|
-
scope
|
|
36331
|
+
scope,
|
|
36332
|
+
pos,
|
|
36333
|
+
edit
|
|
36266
36334
|
);
|
|
36267
36335
|
if (observed.pointer.type !== "keyOf" /* KeyOf */)
|
|
36268
|
-
throw new CompileError(
|
|
36269
|
-
"OnChanged requires a declared member slot.",
|
|
36270
|
-
statement.pos
|
|
36271
|
-
);
|
|
36336
|
+
throw new CompileError("OnChanged requires a declared member slot.", pos);
|
|
36272
36337
|
if (!observed.pointer.memberId)
|
|
36273
36338
|
throw new CompileError(
|
|
36274
36339
|
"OnChanged cannot observe a collection entry.",
|
|
36275
|
-
|
|
36340
|
+
pos
|
|
36276
36341
|
);
|
|
36277
36342
|
if (member.computed) {
|
|
36278
36343
|
throw new CompileError(
|
|
36279
36344
|
"OnChanged cannot observe a computed member.",
|
|
36280
|
-
|
|
36345
|
+
pos
|
|
36281
36346
|
);
|
|
36282
36347
|
}
|
|
36283
36348
|
if (member.readonlyDeclaration) {
|
|
36284
36349
|
throw new CompileError(
|
|
36285
36350
|
"OnChanged cannot observe a readonly declaration.",
|
|
36286
|
-
|
|
36351
|
+
pos
|
|
36287
36352
|
);
|
|
36288
36353
|
}
|
|
36289
36354
|
if (observed.type.kind === "delegate" || observed.type.kind === "action") {
|
|
36290
36355
|
throw new CompileError(
|
|
36291
36356
|
"OnChanged cannot observe a callable member.",
|
|
36292
|
-
|
|
36357
|
+
pos
|
|
36293
36358
|
);
|
|
36294
36359
|
}
|
|
36295
36360
|
const writability = observed.userData ? "runtime" /* Runtime */ : observed.writability;
|
|
36296
36361
|
if (writability !== "save" /* Save */ && writability !== "session" /* Session */ && writability !== "runtime" /* Runtime */ && writability !== "local" /* Local */) {
|
|
36297
36362
|
throw new CompileError(
|
|
36298
36363
|
"OnChanged requires a member whose storage is writable at gameplay runtime.",
|
|
36299
|
-
|
|
36364
|
+
pos
|
|
36300
36365
|
);
|
|
36301
36366
|
}
|
|
36302
36367
|
this.validateConstructorWriteTarget(
|
|
36303
36368
|
observed.pointer,
|
|
36304
|
-
"subscribe to",
|
|
36305
|
-
|
|
36369
|
+
edit.kind === "clearChangeListeners" /* ClearChangeListeners */ ? "clear listeners on" : "subscribe to",
|
|
36370
|
+
pos,
|
|
36306
36371
|
scope,
|
|
36307
36372
|
false
|
|
36308
36373
|
);
|
|
36309
|
-
if (!statement.value) {
|
|
36310
|
-
throw new CompileError(
|
|
36311
|
-
"OnChanged subscription requires a handler member.",
|
|
36312
|
-
statement.pos
|
|
36313
|
-
);
|
|
36314
|
-
}
|
|
36315
36374
|
const storageType = this.assignmentStorageType(
|
|
36316
|
-
|
|
36375
|
+
onChanged.receiver,
|
|
36317
36376
|
observed,
|
|
36318
36377
|
null,
|
|
36319
36378
|
scope
|
|
36320
36379
|
);
|
|
36321
|
-
|
|
36322
|
-
|
|
36323
|
-
|
|
36324
|
-
};
|
|
36325
|
-
const listener = this.resolveActionListener(
|
|
36326
|
-
statement.value,
|
|
36327
|
-
expected,
|
|
36328
|
-
scope,
|
|
36329
|
-
true,
|
|
36330
|
-
true
|
|
36331
|
-
);
|
|
36332
|
-
return {
|
|
36333
|
-
type: statement.op === "+=" ? "addChangeListener" /* AddChangeListener */ : "removeChangeListener" /* RemoveChangeListener */,
|
|
36334
|
-
target: {
|
|
36380
|
+
return this.changeListenerInstruction(
|
|
36381
|
+
edit,
|
|
36382
|
+
{
|
|
36335
36383
|
owner: observed.pointer.keyOf.pointer,
|
|
36336
36384
|
memberId: member.canonicalMemberId ?? observed.pointer.memberId,
|
|
36337
36385
|
typeInfo: toWireType(storageType, this.project),
|
|
36338
36386
|
writability
|
|
36339
36387
|
},
|
|
36340
|
-
|
|
36341
|
-
|
|
36388
|
+
storageType,
|
|
36389
|
+
scope,
|
|
36390
|
+
pos
|
|
36391
|
+
);
|
|
36342
36392
|
}
|
|
36343
36393
|
/**
|
|
36344
36394
|
* P106. A getter subscription lives only in the running client's memory:
|
|
36345
36395
|
* nothing persists it, so construction replay would silently drop one.
|
|
36346
36396
|
*/
|
|
36347
|
-
|
|
36397
|
+
resolveGetterChangeListener(getterRead, observed, pointer, member, scope, pos, edit) {
|
|
36348
36398
|
if (pointer.dispatch === "base") {
|
|
36349
36399
|
throw new CompileError(
|
|
36350
36400
|
`OnChanged can't observe a base getter. Subscribe to '${member.name}' on the instance.`,
|
|
36351
|
-
|
|
36401
|
+
pos
|
|
36352
36402
|
);
|
|
36353
36403
|
}
|
|
36354
36404
|
if (pointer.receiver.kind !== "instance") {
|
|
36355
36405
|
throw new CompileError(
|
|
36356
36406
|
`OnChanged requires an instance receiver for getter '${member.name}'.`,
|
|
36357
|
-
|
|
36407
|
+
pos
|
|
36358
36408
|
);
|
|
36359
36409
|
}
|
|
36360
36410
|
const kind = this.context.kind;
|
|
36361
36411
|
if (isNeoScriptConstructionDocumentKind(kind) || kind === "migration") {
|
|
36362
36412
|
throw new CompileError(
|
|
36363
|
-
`A getter subscription lives only for the play session, so ${dialogueBodyKindLabel(kind)} can't make one. Subscribe from a function.`,
|
|
36364
|
-
|
|
36365
|
-
);
|
|
36366
|
-
}
|
|
36367
|
-
if (!statement.value) {
|
|
36368
|
-
throw new CompileError(
|
|
36369
|
-
"OnChanged subscription requires a handler member.",
|
|
36370
|
-
statement.pos
|
|
36413
|
+
edit.kind === "clearChangeListeners" /* ClearChangeListeners */ ? `A getter subscription lives only for the play session, so ${dialogueBodyKindLabel(kind)} can't clear one. Clear from a function.` : `A getter subscription lives only for the play session, so ${dialogueBodyKindLabel(kind)} can't make one. Subscribe from a function.`,
|
|
36414
|
+
pos
|
|
36371
36415
|
);
|
|
36372
36416
|
}
|
|
36373
36417
|
const getterType = this.assignmentStorageType(
|
|
@@ -36376,23 +36420,36 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
|
|
|
36376
36420
|
null,
|
|
36377
36421
|
scope
|
|
36378
36422
|
);
|
|
36379
|
-
|
|
36380
|
-
|
|
36381
|
-
{
|
|
36382
|
-
scope,
|
|
36383
|
-
true,
|
|
36384
|
-
true
|
|
36385
|
-
);
|
|
36386
|
-
return {
|
|
36387
|
-
type: statement.op === "+=" ? "addChangeListener" /* AddChangeListener */ : "removeChangeListener" /* RemoveChangeListener */,
|
|
36388
|
-
target: {
|
|
36423
|
+
return this.changeListenerInstruction(
|
|
36424
|
+
edit,
|
|
36425
|
+
{
|
|
36389
36426
|
owner: pointer.receiver.pointer,
|
|
36390
36427
|
memberId: member.canonicalMemberId ?? pointer.memberId,
|
|
36391
36428
|
typeInfo: toWireType(getterType, this.project),
|
|
36392
36429
|
writability: "session" /* Session */
|
|
36393
36430
|
},
|
|
36394
|
-
|
|
36395
|
-
|
|
36431
|
+
getterType,
|
|
36432
|
+
scope,
|
|
36433
|
+
pos
|
|
36434
|
+
);
|
|
36435
|
+
}
|
|
36436
|
+
changeListenerInstruction(edit, target, observedType, scope, pos) {
|
|
36437
|
+
if (edit.kind === "clearChangeListeners" /* ClearChangeListeners */)
|
|
36438
|
+
return { type: edit.kind, target };
|
|
36439
|
+
if (!edit.handler) {
|
|
36440
|
+
throw new CompileError(
|
|
36441
|
+
"OnChanged subscription requires a handler member.",
|
|
36442
|
+
pos
|
|
36443
|
+
);
|
|
36444
|
+
}
|
|
36445
|
+
const listener = this.resolveActionListener(
|
|
36446
|
+
edit.handler,
|
|
36447
|
+
{ kind: "action", parameterTypes: [observedType] },
|
|
36448
|
+
scope,
|
|
36449
|
+
true,
|
|
36450
|
+
true
|
|
36451
|
+
);
|
|
36452
|
+
return { type: edit.kind, target, listener };
|
|
36396
36453
|
}
|
|
36397
36454
|
/**
|
|
36398
36455
|
* P62 §3.2. `+=` / `-=` on an NSAction member are subscription operators,
|
|
@@ -38510,6 +38567,8 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
|
|
|
38510
38567
|
entryUserData
|
|
38511
38568
|
};
|
|
38512
38569
|
}
|
|
38570
|
+
if (name === "OnChanged")
|
|
38571
|
+
throw new CompileError(ON_CHANGED_OPERATIONS_ERROR, pos);
|
|
38513
38572
|
throw new CompileError(
|
|
38514
38573
|
`Cannot read member '${name}' on ${this.describe(receiver.type)}`,
|
|
38515
38574
|
pos
|
|
@@ -55688,7 +55747,7 @@ var SPECIALIZED_RELATION_KINDS = /* @__PURE__ */ new Set([
|
|
|
55688
55747
|
|
|
55689
55748
|
// ../packages/manifest/schema-contract/registry.mjs
|
|
55690
55749
|
var PROJECT_SCHEMA_CONTRACT = Object.freeze({
|
|
55691
|
-
cliVersion: "0.
|
|
55750
|
+
cliVersion: "0.75.0",
|
|
55692
55751
|
projectFileUploadBatchSize: 32,
|
|
55693
55752
|
documentRecords: {
|
|
55694
55753
|
member: {
|
package/package.json
CHANGED
|
@@ -118,7 +118,7 @@ wrappers.
|
|
|
118
118
|
The marker near the top of `SKILL.md` must exactly match the package version:
|
|
119
119
|
|
|
120
120
|
```html
|
|
121
|
-
<!-- reviewed-through-cli: 0.
|
|
121
|
+
<!-- reviewed-through-cli: 0.75.0 -->
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
The quoted version above is checked too, so this instruction cannot go stale
|
|
@@ -557,7 +557,8 @@ receiver.Handler`; use `-=` to remove the same receiver/handler pair. Handlers
|
|
|
557
557
|
are noncapturing, return void, and accept the field's type. Bindings to Session
|
|
558
558
|
receivers stay Session-only. Gameplay hosts dispatch after the write batch.
|
|
559
559
|
Authoring, replay, migration, and dialogue dry-run hosts retain wiring without
|
|
560
|
-
invoking callbacks.
|
|
560
|
+
invoking callbacks. `owner.Field.OnChanged.Clear()` removes every handler,
|
|
561
|
+
including `@listeners` defaults, in both Save and Session wiring.
|
|
561
562
|
|
|
562
563
|
Instance getters take the same syntax, for example
|
|
563
564
|
`this._quantity.Value.OnChanged += this.QuantityChanged`. A getter
|
|
@@ -566,7 +567,8 @@ function, effect, lifecycle hook, or dialogue action, never a constructor,
|
|
|
566
567
|
initializer, or migration. Subscribing reads the getter once. The SDK calls the
|
|
567
568
|
handler with a fresh result whenever something that read depended on changes,
|
|
568
569
|
even if the result is equal, and several changes before the next read call it
|
|
569
|
-
once.
|
|
570
|
+
once. `Clear()` ends the subscription, and a constructor can't call it on a
|
|
571
|
+
getter. Static getters, `base.Value`, and `@listeners` can't observe a getter.
|
|
570
572
|
`neo test` dispatches getter subscriptions the same way, treating the test body
|
|
571
573
|
as the host: handlers run after each of its statements, so the next statement
|
|
572
574
|
sees their effects. A getter that reads through `Clone()` or a grid query can
|