Skip to content

Commit

Permalink
impl of array like reactive container, which only allows appending. A…
Browse files Browse the repository at this point in the history
…lso refactored most run classes to use reactive appendable arrays
  • Loading branch information
OliverGrack committed Feb 21, 2025
1 parent 82abda8 commit 195e419
Show file tree
Hide file tree
Showing 19 changed files with 1,160 additions and 558 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
".*Styles",
".*Class",
".*ClassName"
]
],
"cSpell.words": ["Arrayish"]
}
42 changes: 42 additions & 0 deletions src/lib/parser/recording-files/combine-recording-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { HeroStateField } from '../hero-state/hero-states';
import { PlayerDataField } from '../player-data';
import { RecordingFileVersion } from '../recording-file-version';
import { FrameEndEvent, PlayerPositionEvent, SceneEvent } from './events';
import { PlayerDataEvent } from './events/player-data-event';
import { HeroStateEvent } from './recording';

export class CombineRecordingsContext {
msIntoGame = 0;
lastTimestamp: number = 0;

isPaused = true;
isTransitioning = false;

previousPlayerDataEventsByField = new Map<PlayerDataField, PlayerDataEvent<PlayerDataField>>();

getPreviousPlayerData = <TField extends PlayerDataField>(field: TField) => {
return this.previousPlayerDataEventsByField.get(field) as PlayerDataEvent<TField> | undefined;
};

previousHeroStateByField = new Map<HeroStateField, HeroStateEvent>();
getPreviousHeroState = (field: HeroStateField) => {
return this.previousHeroStateByField.get(field);
};

createEndFrameEvent = false;

previousPlayerPositionEvent: PlayerPositionEvent | null = null;
previousPositionEventWithChangedPosition: PlayerPositionEvent | null = null;
previousPlayerPositionEventWithMapPosition: PlayerPositionEvent | null = null;
previousFrameEndEvent: FrameEndEvent | null = null;
previousSceneEvent: SceneEvent | null = null;

recordingFileVersion: RecordingFileVersion = '0.0.0';

visitedScenesToCheckIfInPlayerData = [] as { sceneName: string; msIntoGame: number }[];

allModVersions = new Map<string, Set<string>>();
allHkVizModVersions = new Set<string>();

hasCreatedFirstEndFrameEvent = false;
}
708 changes: 361 additions & 347 deletions src/lib/parser/recording-files/combine-recordings.ts

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions src/lib/parser/recording-files/freezable-signal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Accessor, createSignal, Setter } from 'solid-js';

const frozenSetter = () => {
throw new Error('Cannot set value of frozen signal');
};

const frozenFrozen = () => {
return;
};

export interface FreezableSignal<T> {
get: Accessor<T>;
set: Setter<T>;
freeze(): void;
}

/**
* A signal that can be frozen. When frozen, the value is fixed and cannot be changed.
* Therefore, will not contribute to the reactive graph.
*
* If already frozen when created, the signal will never be created.
*/
export function createFreezableSignal<T>(value: T, frozen: boolean) {
if (frozen) {
return { get: () => value, set: frozenSetter, freeze: frozenFrozen };
}

let _frozen = false;
let _value = value;
const [_get, _set] = createSignal<T>(value);

function get(): T {
if (_frozen) {
return value;
}
return _get();
}
const set: Setter<T> = ((v: any) => {
if (_frozen) {
throw new Error('Cannot set value of frozen signal');
}
return _set(v);
}) as any;

function freeze() {
if (_frozen) {
return;
}
_frozen = true;
_value = _get();
}

return { get, set, freeze };
}
93 changes: 51 additions & 42 deletions src/lib/parser/recording-files/recording-file-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,29 @@ function parseVector2_v1(str: string, factor = 1) {
);
}

export function parseRecordingFile(recordingFileContent: string, combinedPartNumber: number): ParsedRecording {
const lines = recordingFileContent.split('\n');
const events: RecordingEvent[] = [];
let unknownEvents = 0;
let parsingErrors = 0;
export class ParseRecordingFileContext {
unknownEvents = 0;
parsingErrors = 0;

let lastSceneEvent: SceneEvent | undefined = undefined;
let previousPlayerPosition: Vector2 | undefined = undefined;
let previousPlayerPositionEvent: PlayerPositionEvent | null = null;
let previousTimestamp: number | undefined = undefined;
lastSceneEvent: SceneEvent | undefined = undefined;
previousPlayerPosition: Vector2 | undefined = undefined;
previousPlayerPositionEvent: PlayerPositionEvent | null = null;
previousTimestamp: number | undefined = undefined;

// defaults to 0.0.0 since in early version of the mod, the version was only
// written at the beginning of a session, not for each part
let currentRecordingFileVersion: RecordingFileVersion = '0.0.0';
currentRecordingFileVersion: RecordingFileVersion = '0.0.0';
}

export function parseRecordingFile(
recordingFileContent: string,
combinedPartNumber: number,
context?: ParseRecordingFileContext,
): RecordingEvent[] {
const lines = recordingFileContent.split('\n');
const events: RecordingEvent[] = [];

const ctx = context ?? new ParseRecordingFileContext();

let i = 0;
LINE_LOOP: for (let line of lines) {
Expand All @@ -84,19 +93,19 @@ export function parseRecordingFile(recordingFileContent: string, combinedPartNum

let timestamp: number;
if (timestampStr == null || timestampStr === '') {
if (previousTimestamp === undefined) {
if (ctx.previousTimestamp === undefined) {
throw new Error('Relative timestamp found, but no previous timestamp found');
}
timestamp = previousTimestamp!;
timestamp = ctx.previousTimestamp!;
} else if (isRelativeTimestamp) {
if (previousTimestamp == null) {
if (ctx.previousTimestamp == null) {
throw new Error('Relative timestamp found, but no previous timestamp found');
}
timestamp = previousTimestamp + parseInt(timestampStr);
timestamp = ctx.previousTimestamp + parseInt(timestampStr);
} else {
timestamp = parseInt(timestampStr);
}
previousTimestamp = timestamp;
ctx.previousTimestamp = timestamp;

// ------ EVENT TYPE ------
const partialEventType = eventType[0] as PartialEventPrefix;
Expand All @@ -114,7 +123,7 @@ export function parseRecordingFile(recordingFileContent: string, combinedPartNum
field,

value,
previousPlayerPositionEvent: previousPlayerPositionEvent,
previousPlayerPositionEvent: ctx.previousPlayerPositionEvent,
previousPlayerDataEventOfField: null, // filled in combiner
}),
);
Expand All @@ -133,7 +142,7 @@ export function parseRecordingFile(recordingFileContent: string, combinedPartNum
timestamp,
field,
value: args[0] === '1',
previousPlayerPositionEvent: previousPlayerPositionEvent,
previousPlayerPositionEvent: ctx.previousPlayerPositionEvent,
}),
);
continue LINE_LOOP;
Expand All @@ -150,22 +159,22 @@ export function parseRecordingFile(recordingFileContent: string, combinedPartNum
const eventTypePrefix = eventType as EventPrefix;
switch (eventTypePrefix) {
case EVENT_PREFIXES.SCENE_CHANGE: {
lastSceneEvent = new SceneEvent({
ctx.lastSceneEvent = new SceneEvent({
timestamp,
sceneName: args[0]!,
originOffset: undefined,
sceneSize: undefined,
});
previousPlayerPosition = undefined;
events.push(lastSceneEvent);
ctx.previousPlayerPosition = undefined;
events.push(ctx.lastSceneEvent);
break;
}
case EVENT_PREFIXES.ROOM_DIMENSIONS: {
if (lastSceneEvent) {
if (ctx.lastSceneEvent) {
let originOffset: Vector2;
let sceneSize: Vector2;

if (isVersion0xx(currentRecordingFileVersion)) {
if (isVersion0xx(ctx.currentRecordingFileVersion)) {
originOffset = parseVector2_v0(args[0]!, args[1]!);
sceneSize = parseVector2_v0(args[2]!, args[3]!);
} else {
Expand All @@ -176,34 +185,34 @@ export function parseRecordingFile(recordingFileContent: string, combinedPartNum
// for some reason in Abyss_10 (the scene right to the light house),
// the origin offset is always first set to correct values, and then shortly after to zero
if (
(!lastSceneEvent.originOffset && !lastSceneEvent.sceneSize) ||
(!ctx.lastSceneEvent.originOffset && !ctx.lastSceneEvent.sceneSize) ||
(!originOffset.isZero() && !sceneSize.isZero())
) {
lastSceneEvent.originOffset = originOffset;
lastSceneEvent.sceneSize = sceneSize;
ctx.lastSceneEvent.originOffset = originOffset;
ctx.lastSceneEvent.sceneSize = sceneSize;
}
}
break;
}
case EVENT_PREFIXES.ENTITY_POSITIONS: {
if (lastSceneEvent) {
if (ctx.lastSceneEvent) {
const position: Vector2 | undefined =
args[0] === '='
? previousPlayerPosition
: isVersion0xx(currentRecordingFileVersion)
? ctx.previousPlayerPosition
: isVersion0xx(ctx.currentRecordingFileVersion)
? parseVector2_v0(args[0]!, args[1]!)
: parseVector2_v1(args[0]!, 1 / 10);
if (!position) {
continue;
// throw new Error('Could not assign player position to player position event');
}
previousPlayerPosition = position;
previousPlayerPositionEvent = new PlayerPositionEvent({
ctx.previousPlayerPosition = position;
ctx.previousPlayerPositionEvent = new PlayerPositionEvent({
timestamp,
position,
sceneEvent: lastSceneEvent,
sceneEvent: ctx.lastSceneEvent,
});
events.push(previousPlayerPositionEvent);
events.push(ctx.previousPlayerPositionEvent);
}
break;
}
Expand Down Expand Up @@ -240,18 +249,18 @@ export function parseRecordingFile(recordingFileContent: string, combinedPartNum
const version = args[0]!;

if (isKnownRecordingFileVersion(version)) {
currentRecordingFileVersion = version;
ctx.currentRecordingFileVersion = version;
} else {
console.error(
`Unknown recording file version ${version} falling back to newest known version ${newestRecordingFileVersion}`,
);
currentRecordingFileVersion = newestRecordingFileVersion;
ctx.currentRecordingFileVersion = newestRecordingFileVersion;
}

events.push(
new RecordingFileVersionEvent({
timestamp,
version: currentRecordingFileVersion as RecordingFileVersion,
version: ctx.currentRecordingFileVersion as RecordingFileVersion,
}),
);

Expand All @@ -269,7 +278,7 @@ export function parseRecordingFile(recordingFileContent: string, combinedPartNum
events.push(
new SpellFireballEvent({
timestamp,
previousPlayerPositionEvent: previousPlayerPositionEvent,
previousPlayerPositionEvent: ctx.previousPlayerPositionEvent,
}),
);
break;
Expand All @@ -278,7 +287,7 @@ export function parseRecordingFile(recordingFileContent: string, combinedPartNum
events.push(
new SpellUpEvent({
timestamp,
previousPlayerPositionEvent: previousPlayerPositionEvent,
previousPlayerPositionEvent: ctx.previousPlayerPositionEvent,
}),
);
break;
Expand All @@ -287,7 +296,7 @@ export function parseRecordingFile(recordingFileContent: string, combinedPartNum
events.push(
new SpellDownEvent({
timestamp,
previousPlayerPositionEvent: previousPlayerPositionEvent,
previousPlayerPositionEvent: ctx.previousPlayerPositionEvent,
}),
);
break;
Expand Down Expand Up @@ -347,20 +356,20 @@ export function parseRecordingFile(recordingFileContent: string, combinedPartNum
default: {
typeCheckNever(eventTypePrefix);
console.log(`Unexpected event type |${eventType}| ignoring line |${line}|`);
unknownEvents++;
ctx.unknownEvents++;
}
}
} catch (e) {
console.error(
`Error while parsing line ${i}: |${line}| using file version ${currentRecordingFileVersion} in part number ${combinedPartNumber}`,
`Error while parsing line ${i}: |${line}| using file version ${ctx.currentRecordingFileVersion} in part number ${combinedPartNumber}`,
e,
);

((window as any).errorLines = (window as any).errorLines ?? []).push(line);
parsingErrors++;
ctx.parsingErrors++;
}
i++;
}

return new ParsedRecording(events, unknownEvents, parsingErrors, combinedPartNumber);
return events;
}
6 changes: 4 additions & 2 deletions src/lib/parser/recording-files/recording-splits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
playerDataFields,
} from '../player-data/player-data';
import { type PlayerPositionEvent } from './events/player-position-event';
import { type CombinedRecording } from './recording';
import { RecordingEvent, type CombinedRecording } from './recording';

Check failure on line 21 in src/lib/parser/recording-files/recording-splits.ts

View workflow job for this annotation

GitHub Actions / Lint Code

'CombinedRecording' is defined but never used. Allowed unused vars must match /^_/u
import { assertNever, parseHtmlEntities } from '../util';

export const recordingSplitGroups = [
Expand Down Expand Up @@ -111,7 +111,9 @@ function createRecordingSplitFromEnemy(
// }
// }

export function createRecordingSplits(recording: CombinedRecording): RecordingSplit[] {
export interface CreatingRecordingSplitsContext {}

export function createRecordingSplits(events: RecordingEvent[]): RecordingSplit[] {
const splits: RecordingSplit[] = [];

for (const field of Object.values(playerDataFields.byFieldName)) {
Expand Down
Loading

0 comments on commit 195e419

Please sign in to comment.