Skip to content

Commit

Permalink
Fix to allow variable observers to run new ink more robustly by havin…
Browse files Browse the repository at this point in the history
…g the callback fire later
  • Loading branch information
smwhr committed Aug 3, 2024
1 parent caa804e commit 8e8301d
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 23 deletions.
4 changes: 3 additions & 1 deletion src/engine/NativeFunctionCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ export class NativeFunctionCall extends InkObject {
for (let p of parameters) {
if (p instanceof Void)
throw new StoryException(
'Attempting to perform operation on a void value. Did you forget to "return" a value from a function you called here?'
"Attempting to perform " +
this.name +
' on a void value. Did you forget to "return" a value from a function you called here?'
);
if (p instanceof ListValue) hasList = true;
}
Expand Down
28 changes: 26 additions & 2 deletions src/engine/Story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,9 @@ export class Story extends InkObject {
this._state.ResetOutput();

if (this._recursiveContinueCount == 1)
this._state.variablesState.batchObservingVariableChanges = true;
this._state.variablesState.StartVariableObservation();
} else if (this._asyncContinueActive && !isAsyncTimeLimited) {
this._asyncContinueActive = false;
}

let durationStopwatch = new Stopwatch();
Expand Down Expand Up @@ -400,6 +402,8 @@ export class Story extends InkObject {

durationStopwatch.Stop();

let changedVariablesToObserve: Map<string, any> | null = null;

if (outputStreamEndsInNewline || !this.canContinue) {
if (this._stateSnapshotAtLastNewline !== null) {
this.RestoreStateSnapshot();
Expand Down Expand Up @@ -439,7 +443,7 @@ export class Story extends InkObject {
this._sawLookaheadUnsafeFunctionAfterNewline = false;

if (this._recursiveContinueCount == 1)
this._state.variablesState.batchObservingVariableChanges = false;
changedVariablesToObserve = this._state.variablesState.CompleteVariableObservation();

this._asyncContinueActive = false;
if (this.onDidContinue !== null) this.onDidContinue();
Expand Down Expand Up @@ -494,6 +498,12 @@ export class Story extends InkObject {
throw new StoryException(sb.toString());
}
}
if (
changedVariablesToObserve != null &&
Object.keys(changedVariablesToObserve).length > 0
) {
this._state.variablesState.NotifyObservers(changedVariablesToObserve);
}
}

public ContinueSingleStep() {
Expand Down Expand Up @@ -1836,6 +1846,20 @@ export class Story extends InkObject {

let foundExternal = typeof funcDef !== "undefined";

if (
foundExternal &&
!funcDef!.lookAheadSafe &&
this._state.inStringEvaluation
) {
this.Error(
"External function " +
funcName +
' could not be called because 1) it wasn\'t marked as lookaheadSafe when BindExternalFunction was called and 2) the story is in the middle of string generation, either because choice text is being generated, or because you have ink like "hello {func()}". You can work around this by generating the result of your function into a temporary variable before the string or choice gets generated: ~ temp x = ' +
funcName +
"()"
);
}

if (
foundExternal &&
!funcDef!.lookAheadSafe &&
Expand Down
12 changes: 12 additions & 0 deletions src/engine/StoryState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,18 @@ export class StoryState {
}
}

get previousPathString() {
let pointer = this.previousPointer;
if (pointer.isNull) {
return null;
} else {
if (pointer.path === null) {
return throwNullException("previousPointer.path");
}
return pointer.path.toString();
}
}

get currentPointer() {
return this.callStack.currentElement.currentPointer.copy();
}
Expand Down
48 changes: 28 additions & 20 deletions src/engine/VariablesState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,35 @@ export class VariablesState extends VariablesStateAccessor<

public patch: StatePatch | null = null;

get batchObservingVariableChanges() {
return this._batchObservingVariableChanges;
public StartVariableObservation() {
this._batchObservingVariableChanges = true;
this._changedVariablesForBatchObs = new Set();
}
set batchObservingVariableChanges(value: boolean) {
this._batchObservingVariableChanges = value;
if (value) {
this._changedVariablesForBatchObs = new Set();
} else {
if (this._changedVariablesForBatchObs != null) {
for (let variableName of this._changedVariablesForBatchObs) {
let currentValue = this._globalVariables.get(variableName);
if (!currentValue) {
throwNullException("currentValue");
} else {
this.variableChangedEvent(variableName, currentValue);
}
}

this._changedVariablesForBatchObs = null;
public CompleteVariableObservation(): Map<string, any> {
this._batchObservingVariableChanges = false;
let changedVars = new Map<string, any>();
if (this._changedVariablesForBatchObs != null) {
for (let variableName of this._changedVariablesForBatchObs) {
let currentValue = this._globalVariables.get(variableName) as InkObject;
this.variableChangedEvent(variableName, currentValue);
}
}
// Patch may still be active - e.g. if we were in the middle of a background save
if (this.patch != null) {
for (let variableName of this.patch.changedVariables) {
let patchedVal = this.patch.TryGetGlobal(variableName, null);
if (patchedVal.exists) changedVars.set(variableName, patchedVal);
}
}
this._changedVariablesForBatchObs = null;
return changedVars;
}

public NotifyObservers(changedVars: Map<string, any>){
for (const [key, value] of changedVars) {
this.variableChangedEvent(key, value);
}
}

get callStack() {
Expand All @@ -77,8 +85,6 @@ export class VariablesState extends VariablesStateAccessor<
this._callStack = callStack;
}

private _batchObservingVariableChanges: boolean = false;

// the original code uses a magic getter and setter for global variables,
// allowing things like variableState['varname]. This is not quite possible
// in js without a Proxy, so it is replaced with this $ function.
Expand Down Expand Up @@ -429,7 +435,7 @@ export class VariablesState extends VariablesStateAccessor<
oldValue !== null &&
value !== oldValue.result
) {
if (this.batchObservingVariableChanges) {
if (this._batchObservingVariableChanges) {
if (this._changedVariablesForBatchObs === null) {
return throwNullException("this._changedVariablesForBatchObs");
}
Expand Down Expand Up @@ -495,4 +501,6 @@ export class VariablesState extends VariablesStateAccessor<
private _callStack: CallStack;
private _changedVariablesForBatchObs: Set<string> | null = new Set();
private _listDefsOrigin: ListDefinitionsOrigin | null;

private _batchObservingVariableChanges: boolean = false;
}
1 change: 1 addition & 0 deletions src/tests/inkfiles/original/lists/list_range.ink
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ VAR all = ()
{all}
{LIST_RANGE(all, 2, 3)}
{LIST_RANGE(LIST_ALL(Numbers), Two, Six)}
{LIST_RANGE(LIST_ALL(Numbers), Currency, Three)}
{LIST_RANGE(LIST_ALL(Numbers), 2, Four)} // mix int and list
{LIST_RANGE(LIST_ALL(Numbers), Two, 5)} // mix list and int
{LIST_RANGE((Pizza, Pasta), -1, 100)} // allow out of range
1 change: 1 addition & 0 deletions src/tests/specs/ink/Lists.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ describe("Lists", () => {
`Pound, Pizza, Euro, Pasta, Dollar, Curry, Paella
Euro, Pasta, Dollar, Curry
Two, Three, Four, Five, Six
One, Two, Three
Two, Three, Four
Two, Three, Four, Five
Pizza, Pasta
Expand Down

0 comments on commit 8e8301d

Please sign in to comment.