Skip to content

Commit

Permalink
fix(util): split up state and script utils
Browse files Browse the repository at this point in the history
  • Loading branch information
ssube committed Jun 6, 2021
1 parent edfac98 commit 0cf60a6
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 160 deletions.
2 changes: 1 addition & 1 deletion src/script/signal/actor/ActorStep.ts
Expand Up @@ -6,7 +6,7 @@ import { ScriptContext, ScriptTarget } from '../../../service/script';
import { ShowVolume, StateSource } from '../../../util/actor';
import { getKey } from '../../../util/collection/map';
import { STAT_HEALTH } from '../../../util/constants';
import { getVerbScripts } from '../../../util/state';
import { getVerbScripts } from '../../../util/script';

export async function SignalActorStep(this: ScriptTarget, context: ScriptContext): Promise<void> {
context.logger.debug({
Expand Down
3 changes: 2 additions & 1 deletion src/service/script/LocalScript.ts
Expand Up @@ -17,7 +17,8 @@ import { VerbActorMove } from '../../script/verb/ActorMove';
import { VerbActorTake } from '../../script/verb/ActorTake';
import { VerbActorUse } from '../../script/verb/ActorUse';
import { VerbActorWait } from '../../script/verb/ActorWait';
import { getSignalScripts, getVerbScripts, SearchParams } from '../../util/state';
import { getSignalScripts, getVerbScripts } from '../../util/script';
import { SearchParams } from '../../util/state';

/**
* Common scripts, built into the engine and always available.
Expand Down
174 changes: 90 additions & 84 deletions src/service/state/LocalState.ts
Expand Up @@ -46,7 +46,8 @@ import {
SIGNAL_STEP,
VERB_PREFIX,
} from '../../util/constants';
import { findRoom, getVerbScripts, SearchParams, searchState } from '../../util/state';
import { getVerbScripts } from '../../util/script';
import { findRoom, SearchParams, searchState } from '../../util/state';
import { debugState, graphState } from '../../util/state/debug';
import { StateEntityGenerator } from '../../util/state/EntityGenerator';
import {
Expand Down Expand Up @@ -134,6 +135,50 @@ export class LocalStateService implements StateService {
this.event.removeGroup(this);
}

// #region event handlers
/**
* Step the internal world state, simulating some turns and time passing.
*/
public async onCommand(event: ActorCommandEvent): Promise<void> {
const { actor, command } = event;

this.logger.debug({
actor,
command,
}, 'handling command event');

// handle meta commands
switch (command.verb) {
case META_CREATE:
await this.doCreate(command.target, command.index);
break;
case META_DEBUG:
await this.doDebug();
break;
case META_GRAPH:
await this.doGraph(command.target);
break;
case META_HELP:
await this.doHelp(event);
break;
case META_LOAD:
await this.doLoad(command.target);
break;
case META_QUIT:
await this.doQuit();
break;
case META_SAVE:
await this.doSave(command.target);
break;
case META_WORLDS:
await this.doWorlds();
break;
default: {
await this.doStep(event);
}
}
}

/**
* A new player is joining and their actor must be found or created.
*/
Expand Down Expand Up @@ -213,89 +258,9 @@ export class LocalStateService implements StateService {
this.logger.debug({ world: world.meta.id }, 'registering loaded world');
this.worlds.push(world);
}
// #endregion event handlers

/**
* Step the internal world state, simulating some turns and time passing.
*/
public async onCommand(event: ActorCommandEvent): Promise<void> {
const { actor, command } = event;

this.logger.debug({
actor,
command,
}, 'handling command event');

// handle meta commands
switch (command.verb) {
case META_CREATE:
await this.doCreate(command.target, command.index);
break;
case META_DEBUG:
await this.doDebug();
break;
case META_GRAPH:
await this.doGraph(command.target);
break;
case META_HELP:
await this.doHelp(event);
break;
case META_LOAD:
await this.doLoad(command.target);
break;
case META_QUIT:
await this.doQuit();
break;
case META_SAVE:
await this.doSave(command.target);
break;
case META_WORLDS:
await this.doWorlds();
break;
default: {
await this.doStep(event);
}
}
}

/**
* Perform the next world state step.
*/
public async doStep(event: ActorCommandEvent): Promise<void> {
const { actor, command } = event;

// if there is no world state, there won't be an actor, but this error is more informative
if (isNil(actor) || isNil(this.state)) {
this.event.emit(EVENT_STATE_OUTPUT, {
line: 'meta.step.none',
step: {
time: 0,
turn: 0,
},
volume: ShowVolume.WORLD,
});
return;
}

this.commandBuffer.push(actor, command);
this.logger.debug({
actor: actor.meta.id,
left: this.commandQueue.remaining().map((it) => it.meta.id),
size: this.commandQueue.size,
verb: command.verb,
}, 'pushing command to queue');

// step world after last actor acts
if (this.commandQueue.complete(actor)) {
this.logger.debug({
actor: actor.meta.id,
size: this.commandQueue.size,
verb: command.verb,
}, 'queue completed on command');
const result = await this.step();
this.event.emit(EVENT_STATE_STEP, result);
}
}

// #region meta commands
/**
* Create a new world and invite players to join.
*/
Expand Down Expand Up @@ -543,6 +508,45 @@ export class LocalStateService implements StateService {
});
}

/**
* Perform the next world state step.
*/
public async doStep(event: ActorCommandEvent): Promise<void> {
const { actor, command } = event;

// if there is no world state, there won't be an actor, but this error is more informative
if (isNil(actor) || isNil(this.state)) {
this.event.emit(EVENT_STATE_OUTPUT, {
line: 'meta.step.none',
step: {
time: 0,
turn: 0,
},
volume: ShowVolume.WORLD,
});
return;
}

this.commandBuffer.push(actor, command);
this.logger.debug({
actor: actor.meta.id,
left: this.commandQueue.remaining().map((it) => it.meta.id),
size: this.commandQueue.size,
verb: command.verb,
}, 'pushing command to queue');

// step world after last actor acts
if (this.commandQueue.complete(actor)) {
this.logger.debug({
actor: actor.meta.id,
size: this.commandQueue.size,
verb: command.verb,
}, 'queue completed on command');
const result = await this.step();
this.event.emit(EVENT_STATE_STEP, result);
}
}

public async doWorlds(): Promise<void> {
for (const world of this.worlds) {
this.event.emit(EVENT_STATE_OUTPUT, {
Expand All @@ -559,6 +563,7 @@ export class LocalStateService implements StateService {
});
}
}
// #endregion meta commands

public async step(): Promise<StepResult> {
if (isNil(this.state)) {
Expand Down Expand Up @@ -645,6 +650,7 @@ export class LocalStateService implements StateService {
};
}

// #region state access callbacks
/**
* Handler for a room change from the state helper.
*/
Expand Down Expand Up @@ -686,6 +692,7 @@ export class LocalStateService implements StateService {
volume,
});
}
// #endregion state access callbacks

/**
* Emit changed rooms to relevant actors.
Expand Down Expand Up @@ -727,5 +734,4 @@ export class LocalStateService implements StateService {
throw new Error('actor has not queued a command: ' + actor.meta.id);
}
}

}
69 changes: 69 additions & 0 deletions src/util/script/index.ts
@@ -0,0 +1,69 @@
import { doesExist } from '@apextoaster/js-utils';

import { WorldEntity } from '../../model/entity';
import { Actor, isActor } from '../../model/entity/Actor';
import { isItem, Item } from '../../model/entity/Item';
import { isRoom, Room } from '../../model/entity/Room';
import { SIGNAL_PREFIX, VERB_PREFIX } from '../constants';
import { ScriptMap } from '../types';

interface VerbTarget {
actor?: Actor;
item?: Item;
room?: Room;
}

export function getSignalScripts(target: WorldEntity): ScriptMap {
const scripts: ScriptMap = new Map();

for (const [name, script] of target.scripts) {
if (name.startsWith(SIGNAL_PREFIX)) {
scripts.set(name, script);
}
}

return scripts;
}

/**
* @todo optimize, currently on a hot path
*/
export function getVerbScripts(target: VerbTarget): ScriptMap {
const scripts: ScriptMap = new Map();

if (isActor(target.actor)) {
mergeVerbScripts(scripts, target.actor.scripts);

for (const item of target.actor.items) {
mergeVerbScripts(scripts, item.scripts);
}
}

if (isItem(target.item)) {
mergeVerbScripts(scripts, target.item.scripts);
}

if (isRoom(target.room)) {
mergeVerbScripts(scripts, target.room.scripts);

// TODO: add room items?
}

const scriptNames = Array.from(scripts.keys()); // needs to be pulled AOT since the Map will be mutated
for (const name of scriptNames) {
const script = scripts.get(name);
if (doesExist(script) && script.name.length === 0) {
scripts.delete(name);
}
}

return scripts;
}

export function mergeVerbScripts(target: ScriptMap, source: ScriptMap): void {
for (const [name, script] of source) {
if (name.startsWith(VERB_PREFIX)) {
target.set(name, script);
}
}
}

0 comments on commit 0cf60a6

Please sign in to comment.