mirror of
https://github.com/FullScreenShenanigans/EightBittr.git
synced 2026-08-12 02:18:15 -07:00
chore: refactored ClassCyclr to not extend arrays, with tests (#342)
## Overview
Small cleanup/refactor I've been meaning to do for a while. It's weird
that ClassCyclr adds aritrary properties to arrays. This switches the
internal representation of cycles a bit to use `TimeCycle` objects with
a `classes: ClassesList` property.
And, now `ClassCyclr` is unit tested! 🙌
### PR Checklist
- ~[ ] Fixes #~
- [x] I have run this code to verify it works
- [x] This PR includes unit tests for the code change
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
"Cyclr",
|
||||
"devicelayr",
|
||||
"dpad",
|
||||
"dragonfruit",
|
||||
"Drawr",
|
||||
"Editr",
|
||||
"eightbittr",
|
||||
|
||||
@@ -1,5 +1,376 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { createClassCycler, createStubActor } from "./fakes.test";
|
||||
|
||||
const interval = 8;
|
||||
|
||||
describe("ClassCyclr", () => {
|
||||
it("_", () => {
|
||||
/* ... */
|
||||
describe("addClassCycle", () => {
|
||||
it("immediately runs a first class cycle when the actor does not have any cycles or class name", () => {
|
||||
// Arrange
|
||||
const { classCycler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
// Act
|
||||
classCycler.addClassCycle(actor, ["apple", "banana"], "myCycle", interval);
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("apple");
|
||||
});
|
||||
|
||||
it("immediately runs a first class cycle when the actor does not have any cycles", () => {
|
||||
// Arrange
|
||||
const { classCycler } = createClassCycler();
|
||||
const actor = createStubActor({ className: "initial" });
|
||||
|
||||
// Act
|
||||
classCycler.addClassCycle(actor, ["apple", "banana"], "myCycle", interval);
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("initial apple");
|
||||
});
|
||||
|
||||
it("immediately runs a first class cycle when the actor previously does have cycles", () => {
|
||||
// Arrange
|
||||
const { classCycler } = createClassCycler();
|
||||
const actor = createStubActor({ className: "initial" });
|
||||
|
||||
classCycler.addClassCycle(actor, ["apple", "banana"], "myCycle1", interval);
|
||||
|
||||
// Act
|
||||
classCycler.addClassCycle(actor, ["cherry", "dragonfruit"], "myCycle2", interval);
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("initial apple cherry");
|
||||
});
|
||||
|
||||
it("switches classes when the cycle time ticks", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor({ className: "initial" });
|
||||
|
||||
classCycler.addClassCycle(actor, ["apple", "banana"], "myCycle", interval);
|
||||
|
||||
// Act
|
||||
for (let i = 0; i < interval; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("initial banana");
|
||||
});
|
||||
|
||||
it("does not remove an existing, duplicate class name when the cycle time ticks", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = {
|
||||
className: "initial apple",
|
||||
placed: true,
|
||||
};
|
||||
|
||||
classCycler.addClassCycle(actor, ["apple", "banana"], "myCycle", interval);
|
||||
|
||||
// Act
|
||||
for (let i = 0; i < interval; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("initial apple banana");
|
||||
});
|
||||
|
||||
it("switches classes again when the cycle time ticks twice", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor({ className: "initial" });
|
||||
|
||||
classCycler.addClassCycle(actor, ["apple", "banana", "cherry"], "myCycle", interval);
|
||||
|
||||
// Act
|
||||
for (let i = 0; i < interval * 2; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("initial cherry");
|
||||
});
|
||||
|
||||
it("restarts classes when the cycle time ticks through all classes after no initial class name", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
classCycler.addClassCycle(actor, ["apple", "banana", "cherry"], "myCycle", interval);
|
||||
|
||||
// Act
|
||||
for (let i = 0; i < interval * 3; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("apple");
|
||||
});
|
||||
|
||||
it("restarts classes when the cycle time ticks through all classes after an initial class name", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor({ className: "initial" });
|
||||
|
||||
classCycler.addClassCycle(actor, ["apple", "banana", "cherry"], "myCycle", interval);
|
||||
|
||||
// Act
|
||||
for (let i = 0; i < interval * 3; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("initial apple");
|
||||
});
|
||||
|
||||
it("allows for multiple independent cycles", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
classCycler.addClassCycle(actor, ["apple", "banana"], "myCycle1", interval);
|
||||
classCycler.addClassCycle(
|
||||
actor,
|
||||
["cherry", "dragonfruit"],
|
||||
"myCycle2",
|
||||
interval * 1.5
|
||||
);
|
||||
|
||||
// Act
|
||||
for (let i = 0; i < interval * 3; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("cherry banana");
|
||||
});
|
||||
});
|
||||
|
||||
describe("addClassCycleSynched", () => {
|
||||
it("immediately runs a class cycle when the timeHandler", () => {
|
||||
// Arrange
|
||||
const { classCycler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
// Act
|
||||
classCycler.addClassCycleSynched(actor, ["apple", "banana"], "myCycle", interval);
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("apple");
|
||||
});
|
||||
|
||||
it("does not advance a class cycle when the timeHandler has not yet reached the tick interval", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
// Act
|
||||
classCycler.addClassCycleSynched(actor, ["apple", "banana"], "myCycle", interval);
|
||||
|
||||
for (let i = 0; i < interval - 1; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("apple");
|
||||
});
|
||||
|
||||
it("advances a class cycle when the timeHandler reaches the tick interval", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
// Act
|
||||
classCycler.addClassCycleSynched(actor, ["apple", "banana"], "myCycle", interval);
|
||||
|
||||
for (let i = 0; i < interval; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("banana");
|
||||
});
|
||||
|
||||
it("adds a class when provided as a function that returns a string", () => {
|
||||
// Arrange
|
||||
const { classCycler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
// Act
|
||||
classCycler.addClassCycle(
|
||||
actor,
|
||||
[() => "apple", () => "banana"],
|
||||
"myCycle",
|
||||
interval
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("apple");
|
||||
});
|
||||
|
||||
it("adds a next class when provided as a function that returns a string", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
// Act
|
||||
classCycler.addClassCycle(
|
||||
actor,
|
||||
[() => "apple", () => "banana"],
|
||||
"myCycle",
|
||||
interval
|
||||
);
|
||||
|
||||
for (let i = 0; i < interval; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("banana");
|
||||
});
|
||||
|
||||
it("does not continue cycling when when provided a function that returns true", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
// Act
|
||||
classCycler.addClassCycle(
|
||||
actor,
|
||||
[() => "apple", () => true, () => "cherry"],
|
||||
"myCycle",
|
||||
interval
|
||||
);
|
||||
|
||||
for (let i = 0; i < interval * 2; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("");
|
||||
});
|
||||
|
||||
it("continues cycling when when provided a function that returns false", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
// Act
|
||||
classCycler.addClassCycle(
|
||||
actor,
|
||||
[() => "apple", () => false, () => "cherry"],
|
||||
"myCycle",
|
||||
interval
|
||||
);
|
||||
|
||||
for (let i = 0; i < interval * 2; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("cherry");
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancelAllCycles", () => {
|
||||
it("does not crash when an actor does not yet have cycles", () => {
|
||||
// Arrange
|
||||
const { classCycler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
// Act
|
||||
const act = () => classCycler.cancelAllCycles(actor);
|
||||
|
||||
// Assert
|
||||
expect(act).not.to.throw();
|
||||
});
|
||||
|
||||
it("cancels multiple cycles", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
classCycler.addClassCycle(actor, ["apple", "banana"], "myCycle1", interval);
|
||||
classCycler.addClassCycle(actor, ["cherry", "dragonfruit"], "myCycle2", interval);
|
||||
|
||||
// Act
|
||||
classCycler.cancelClassCycle(actor, "myCycle1");
|
||||
classCycler.cancelClassCycle(actor, "myCycle2");
|
||||
|
||||
for (let i = 0; i < interval; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("apple cherry");
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancelClassCycle", () => {
|
||||
it("does not crash when an actor does not yet have cycles", () => {
|
||||
// Arrange
|
||||
const { classCycler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
// Act
|
||||
const act = () => classCycler.cancelClassCycle(actor, "myCycle");
|
||||
|
||||
// Assert
|
||||
expect(act).not.to.throw();
|
||||
});
|
||||
|
||||
it("cancels a cycle when the cycle has not reached its second iteration", () => {
|
||||
// Arrange
|
||||
const { classCycler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
classCycler.addClassCycle(actor, ["apple", "banana"], "myCycle", interval);
|
||||
|
||||
// Act
|
||||
classCycler.cancelClassCycle(actor, "myCycle");
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("apple");
|
||||
});
|
||||
|
||||
it("cancels a cycle when the cycle has not yet looped", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
classCycler.addClassCycle(actor, ["apple", "banana"], "myCycle", interval);
|
||||
|
||||
for (let i = 0; i < interval; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Act
|
||||
classCycler.cancelClassCycle(actor, "myCycle");
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("banana");
|
||||
});
|
||||
|
||||
it("cancels a cycle when the cycle has looped", () => {
|
||||
// Arrange
|
||||
const { classCycler, timeHandler } = createClassCycler();
|
||||
const actor = createStubActor();
|
||||
|
||||
classCycler.addClassCycle(actor, ["apple", "banana"], "myCycle", interval);
|
||||
|
||||
for (let i = 0; i < interval * 2; i += 1) {
|
||||
timeHandler.advance();
|
||||
}
|
||||
|
||||
// Act
|
||||
classCycler.cancelClassCycle(actor, "myCycle");
|
||||
|
||||
// Assert
|
||||
expect(actor.className).to.be.equal("apple");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,48 +1,11 @@
|
||||
import { NumericCalculator, TimeEvent, TimeHandlr } from "timehandlr";
|
||||
import { NumericCalculator, TimeHandlr } from "timehandlr";
|
||||
|
||||
import {
|
||||
Actor,
|
||||
ClassCalculator,
|
||||
ClassChanger,
|
||||
ClassCyclrSettings,
|
||||
TimeCycle,
|
||||
TimeCycleSettings,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Default classAdd Function.
|
||||
*
|
||||
* @param actor The actor whose class is being modified.
|
||||
* @param className The String to be added to the actor's class.
|
||||
*/
|
||||
const classAddGeneric = (actor: Actor, className: string): void => {
|
||||
actor.className += ` ${className}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default classRemove Function.
|
||||
*
|
||||
* @param actor The actor whose class is being modified.
|
||||
* @param className The String to be removed from the actor's class.
|
||||
*/
|
||||
const classRemoveGeneric = (actor: Actor, className: string): void => {
|
||||
actor.className = actor.className.replace(className, "");
|
||||
};
|
||||
import { Actor, ClassCyclrSettings, ClassesList, TimeCycle } from "./types";
|
||||
|
||||
/**
|
||||
* Cycles through class names using TimeHandlr events.
|
||||
*/
|
||||
export class ClassCyclr {
|
||||
/**
|
||||
* Adds a class to an Actor.
|
||||
*/
|
||||
private readonly classAdd: ClassChanger;
|
||||
|
||||
/**
|
||||
* Removes a class from an Actor.
|
||||
*/
|
||||
private readonly classRemove: ClassChanger;
|
||||
|
||||
/**
|
||||
* Scheduling for dynamically repeating or synchronized events.
|
||||
*/
|
||||
@@ -54,9 +17,6 @@ export class ClassCyclr {
|
||||
* @param settings Settings to be used for initialization.
|
||||
*/
|
||||
public constructor(settings: ClassCyclrSettings) {
|
||||
this.classAdd = settings.classAdd === undefined ? classAddGeneric : settings.classAdd;
|
||||
this.classRemove =
|
||||
settings.classRemove === undefined ? classRemoveGeneric : settings.classRemove;
|
||||
this.timeHandler = settings.timeHandler;
|
||||
}
|
||||
|
||||
@@ -64,28 +24,24 @@ export class ClassCyclr {
|
||||
* Adds a sprite cycle (settings) for an actor, to be referenced by the given
|
||||
* name in the actor's cycles Object.
|
||||
*
|
||||
* @aram actor The object whose class is to be cycled.
|
||||
* @param settings Container for repetition settings, particularly .length.
|
||||
* @param actor The object whose class is to be cycled.
|
||||
* @param classes Classes to cycle through.
|
||||
* @param name Name of the cycle, to be referenced in the actor's cycles.
|
||||
* @param timing How long to wait between classes.
|
||||
*/
|
||||
public addClassCycle(
|
||||
actor: Actor,
|
||||
settings: TimeCycleSettings,
|
||||
classes: ClassesList,
|
||||
name: string,
|
||||
timing: number | NumericCalculator
|
||||
): TimeCycle {
|
||||
if (actor.cycles === undefined) {
|
||||
actor.cycles = {};
|
||||
}
|
||||
) {
|
||||
actor.cycles ??= {};
|
||||
|
||||
this.cancelClassCycle(actor, name);
|
||||
|
||||
// Immediately run the first class cycle, then return
|
||||
settings = actor.cycles[name] = this.setClassCycle(actor, settings, timing);
|
||||
this.cycleClass(actor, settings);
|
||||
|
||||
return settings;
|
||||
// Immediately run the first class cycle
|
||||
const cycle = (actor.cycles[name] = this.startClassCycle(actor, classes, timing));
|
||||
this.cycleClass(actor, cycle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,30 +49,24 @@ export class ClassCyclr {
|
||||
* the given name in the actor's cycles Object, and in tune with all other
|
||||
* cycles of the same period.
|
||||
*
|
||||
* @pram actor The object whose class is to be cycled.
|
||||
* @param settings Container for repetition settings, particularly .length.
|
||||
* @param actor The object whose class is to be cycled.
|
||||
* @param classes Classes to cycle through.
|
||||
* @param name Name of the cycle, to be referenced in the actor's cycles.
|
||||
* @param timing How long to wait between classes.
|
||||
*/
|
||||
public addClassCycleSynched(
|
||||
actor: Actor,
|
||||
settings: TimeCycle,
|
||||
classes: ClassesList,
|
||||
name: string,
|
||||
timing: number | NumericCalculator
|
||||
): TimeCycle {
|
||||
if (actor.cycles === undefined) {
|
||||
actor.cycles = {};
|
||||
}
|
||||
) {
|
||||
actor.cycles ??= {};
|
||||
|
||||
if (typeof name !== "undefined") {
|
||||
this.cancelClassCycle(actor, name);
|
||||
}
|
||||
this.cancelClassCycle(actor, name);
|
||||
|
||||
// Immediately run the first class cycle, then return
|
||||
settings = actor.cycles[name] = this.setClassCycle(actor, settings, timing, true);
|
||||
this.cycleClass(actor, settings);
|
||||
|
||||
return settings;
|
||||
// Immediately synch -and potentially run- the first class cycle
|
||||
const cycle = (actor.cycles[name] = this.startClassCycle(actor, classes, timing, true));
|
||||
this.cycleClass(actor, cycle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,12 +76,12 @@ export class ClassCyclr {
|
||||
* @param actor The actor whose cycle is to be cancelled.
|
||||
* @param name Name of the cycle to be cancelled.
|
||||
*/
|
||||
public cancelClassCycle(actor: Actor, name: string): void {
|
||||
public cancelClassCycle(actor: Actor, name: string) {
|
||||
if (actor.cycles === undefined || !(name in actor.cycles)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cycle: TimeCycle = actor.cycles[name];
|
||||
const cycle = actor.cycles[name];
|
||||
|
||||
if (cycle.event !== undefined) {
|
||||
cycle.event.repeat = 0;
|
||||
@@ -145,74 +95,63 @@ export class ClassCyclr {
|
||||
*
|
||||
* @param actor Actor whose cycles are to be cancelled.
|
||||
*/
|
||||
public cancelAllCycles(actor: Actor): void {
|
||||
public cancelAllCycles(actor: Actor) {
|
||||
if (actor.cycles === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const name in actor.cycles) {
|
||||
if (!{}.hasOwnProperty.call(actor.cycles, name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const cycle: TimeCycle = actor.cycles[name];
|
||||
cycle.length = 1;
|
||||
cycle[0] = false;
|
||||
delete actor.cycles[name];
|
||||
this.cancelClassCycle(actor, name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization utility for sprite cycles of actors. The settings are
|
||||
* added t the right time (immediately if not synched, or on a delay if
|
||||
* added to the right time (immediately if not synched, or on a delay if
|
||||
* synched.
|
||||
*
|
||||
* @param ting The object whose class is to be cycled.
|
||||
* @param settings Container for repetition settings, particularly .length.
|
||||
* @param actor The object whose class is to be cycled.
|
||||
* @param classes Classes to cycle through.
|
||||
* @param timing How often to do the cycle.
|
||||
* @param synched Whether the animations should be synched to their period.
|
||||
* @returns The cycle containing settings and the new event.
|
||||
*/
|
||||
private setClassCycle(
|
||||
private startClassCycle(
|
||||
actor: Actor,
|
||||
settings: TimeCycle,
|
||||
classes: ClassesList,
|
||||
timing: number | NumericCalculator,
|
||||
synched?: boolean
|
||||
): TimeCycle {
|
||||
const timingNumber = TimeEvent.runCalculator(timing);
|
||||
|
||||
// Start off before the beginning of the cycle
|
||||
settings.location = settings.oldClass = -1;
|
||||
) {
|
||||
const cycle: TimeCycle = {
|
||||
classes,
|
||||
location: -1,
|
||||
};
|
||||
|
||||
// Let the object know to start the cycle when needed
|
||||
if (synched) {
|
||||
actor.onActorAdded = (): void => {
|
||||
settings.event = this.timeHandler.addEventIntervalSynched(
|
||||
this.cycleClass,
|
||||
timingNumber,
|
||||
Infinity,
|
||||
actor,
|
||||
settings
|
||||
);
|
||||
};
|
||||
} else {
|
||||
actor.onActorAdded = (): void => {
|
||||
settings.event = this.timeHandler.addEventInterval(
|
||||
this.cycleClass,
|
||||
timingNumber,
|
||||
Infinity,
|
||||
actor,
|
||||
settings
|
||||
);
|
||||
};
|
||||
}
|
||||
actor.onActorAdded = () => {
|
||||
cycle.event = synched
|
||||
? this.timeHandler.addEventIntervalSynched(
|
||||
this.cycleClass,
|
||||
timing,
|
||||
Infinity,
|
||||
actor,
|
||||
cycle
|
||||
)
|
||||
: this.timeHandler.addEventInterval(
|
||||
this.cycleClass,
|
||||
timing,
|
||||
Infinity,
|
||||
actor,
|
||||
cycle
|
||||
);
|
||||
};
|
||||
|
||||
// If it should already start, do that
|
||||
if (actor.placed) {
|
||||
actor.onActorAdded(actor);
|
||||
}
|
||||
|
||||
return settings;
|
||||
return cycle;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,52 +159,43 @@ export class ClassCyclr {
|
||||
* If the next object is === false, or the repeat function returns false,
|
||||
* stop by returning true.
|
||||
*
|
||||
* @param thing The object whose class is to be cycled.
|
||||
* @param settings A container for repetition settings, particularly .length.
|
||||
* @param actor The object whose class is to be cycled.
|
||||
* @param cycle A currently cycling time cycle.
|
||||
* @returns Whether the class cycle should stop (normally false).
|
||||
*/
|
||||
private readonly cycleClass = (
|
||||
actor: Actor | undefined,
|
||||
settings: TimeCycle | undefined
|
||||
): boolean => {
|
||||
private readonly cycleClass = (actor: Actor | undefined, cycle: TimeCycle) => {
|
||||
// If anything has been invalidated, return true to stop
|
||||
if (!actor || actor.removed || !settings?.length) {
|
||||
if (!actor || actor.removed || !cycle.classes.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get rid of the previous class from settings, if it's a String
|
||||
if (
|
||||
settings.oldClass !== undefined &&
|
||||
settings.oldClass !== -1 &&
|
||||
typeof settings[settings.oldClass] === "string"
|
||||
) {
|
||||
this.classRemove(actor, settings[settings.oldClass] as string);
|
||||
// Get rid of the previous class from settings
|
||||
if (cycle.previouslyAdded !== undefined) {
|
||||
actor.className = actor.className.startsWith(cycle.previouslyAdded)
|
||||
? actor.className.slice(cycle.previouslyAdded.length + 1)
|
||||
: actor.className.replace(` ${cycle.previouslyAdded}`, "");
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
// Move to the next location in settings, as a circular list
|
||||
settings.location = (settings.location = (settings.location ?? 0) + 1) % settings.length;
|
||||
cycle.location = ((cycle.location ?? 0) + 1) % cycle.classes.length;
|
||||
|
||||
// Current is the class, bool, or Function currently added and/or run
|
||||
const current: boolean | string | ClassCalculator = settings[settings.location];
|
||||
// Current is the boolean, class, or Function currently added and/or run
|
||||
const current = cycle.classes[cycle.location];
|
||||
if (!current) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const name =
|
||||
current.constructor === Function
|
||||
? (current as ClassCalculator)(actor, settings)
|
||||
: current;
|
||||
|
||||
settings.oldClass = settings.location;
|
||||
const nameNew = typeof current === "function" ? current(actor, cycle) : current;
|
||||
|
||||
// Strings are classes to be added directly
|
||||
if (typeof name === "string") {
|
||||
this.classAdd(actor, name);
|
||||
if (typeof nameNew === "string") {
|
||||
actor.className = actor.className === "" ? nameNew : `${actor.className} ${nameNew}`;
|
||||
cycle.previouslyAdded = nameNew;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Truthy non-String names imply a stop is required
|
||||
return !!name;
|
||||
// Truthy non-string names imply a stop is required
|
||||
cycle.previouslyAdded = undefined;
|
||||
return !!nameNew;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { TimeHandlr } from "timehandlr";
|
||||
|
||||
import { ClassCyclr } from "./ClassCyclr";
|
||||
import { Actor } from "./types";
|
||||
|
||||
export const createClassCycler = () => {
|
||||
const timeHandler = new TimeHandlr();
|
||||
const classCycler = new ClassCyclr({ timeHandler });
|
||||
|
||||
return { classCycler, timeHandler };
|
||||
};
|
||||
|
||||
export const createStubActor = (overrides?: Partial<Actor>) => ({
|
||||
className: "",
|
||||
placed: true,
|
||||
...overrides,
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
import { TimeEvent, TimeHandlr } from "timehandlr";
|
||||
|
||||
/**
|
||||
* Settings to create a class cycling event, commonly as a String[].
|
||||
* Classes to cycle through, commonly as a string[].
|
||||
*/
|
||||
export interface TimeCycleSettings {
|
||||
export interface ClassesList {
|
||||
/**
|
||||
* How many class phases should be cycled through.
|
||||
*/
|
||||
@@ -19,7 +19,12 @@ export interface TimeCycleSettings {
|
||||
/**
|
||||
* Information for a currently cycling time cycle.
|
||||
*/
|
||||
export interface TimeCycle extends TimeCycleSettings {
|
||||
export interface TimeCycle {
|
||||
/**
|
||||
* Classes to cycle through.
|
||||
*/
|
||||
classes: ClassesList;
|
||||
|
||||
/**
|
||||
* The container event using this cycle.
|
||||
*/
|
||||
@@ -31,9 +36,9 @@ export interface TimeCycle extends TimeCycleSettings {
|
||||
location?: number;
|
||||
|
||||
/**
|
||||
* The previous class' index.
|
||||
* The class added by the previous cycle, if after a first cycle.
|
||||
*/
|
||||
oldClass?: number;
|
||||
previouslyAdded?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,14 +54,6 @@ export type TimeCycles = Record<string, TimeCycle>;
|
||||
*/
|
||||
export type ClassCalculator = (actor: Actor, settings: TimeCycle) => string | boolean;
|
||||
|
||||
/**
|
||||
* General-purpose Function to add or remove a class on an Actor.
|
||||
*
|
||||
* @param actor An Actor whose class is to change.
|
||||
* @param className The class to add or remove.
|
||||
*/
|
||||
export type ClassChanger = (actor: Actor, className: string) => void;
|
||||
|
||||
/**
|
||||
* An object that may have classes added or removed, such as in a cycle.
|
||||
*/
|
||||
@@ -91,16 +88,6 @@ export interface Actor {
|
||||
* Settings to initialize a new ClassCyclr.
|
||||
*/
|
||||
export interface ClassCyclrSettings {
|
||||
/**
|
||||
* Adds a class to an Actor (by default, string concatenation).
|
||||
*/
|
||||
classAdd?: ClassChanger;
|
||||
|
||||
/**
|
||||
* Removes a class from an Actor (by default, string removal).
|
||||
*/
|
||||
classRemove?: ClassChanger;
|
||||
|
||||
/**
|
||||
* Scheduling for dynamically repeating or synchronized events.
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { EventCallback, NumericCalculator, TimeEventLike } from "./types";
|
||||
import { EventCallback, NumericCalculator } from "./types";
|
||||
|
||||
/**
|
||||
* An event to be played, including a callback, repetition settings, and arguments.
|
||||
*/
|
||||
export class TimeEvent implements TimeEventLike {
|
||||
export class TimeEvent {
|
||||
/**
|
||||
* The time at which to call this event.
|
||||
*/
|
||||
@@ -12,12 +12,12 @@ export class TimeEvent implements TimeEventLike {
|
||||
/**
|
||||
* Something to run when this event is triggered.
|
||||
*/
|
||||
public callback: () => void;
|
||||
public callback: () => unknown;
|
||||
|
||||
/**
|
||||
* Arguments to be passed to the callback.
|
||||
*/
|
||||
public args?: any[];
|
||||
public args?: unknown[];
|
||||
|
||||
/**
|
||||
* How many times this should repeat. If a Function, called for a return value.
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { TimeEvent } from "./TimeEvent";
|
||||
import {
|
||||
CurrentEvents,
|
||||
EventCallback,
|
||||
NumericCalculator,
|
||||
TimeEventLike,
|
||||
TimeHandlrSettings,
|
||||
} from "./types";
|
||||
import { CurrentEvents, EventCallback, NumericCalculator, TimeHandlrSettings } from "./types";
|
||||
|
||||
/**
|
||||
* Scheduling for dynamically repeating or synchronized events.
|
||||
@@ -87,13 +81,10 @@ export class TimeHandlr {
|
||||
*/
|
||||
public addEventIntervalSynched<Args extends unknown[] = []>(
|
||||
callback: EventCallback<Args>,
|
||||
timeDelay?: number | NumericCalculator,
|
||||
numRepeats?: number | EventCallback,
|
||||
timeDelay: number | NumericCalculator = 1,
|
||||
numRepeats: number | EventCallback = 1,
|
||||
...args: Args
|
||||
): TimeEvent {
|
||||
timeDelay = timeDelay ?? 1;
|
||||
numRepeats = numRepeats ?? 1;
|
||||
|
||||
) {
|
||||
const calcTime = TimeEvent.runCalculator(timeDelay || this.timingDefault);
|
||||
const entryTime = Math.ceil(this.time / calcTime) * calcTime;
|
||||
|
||||
@@ -112,7 +103,7 @@ export class TimeHandlr {
|
||||
/**
|
||||
* Increments time and handles all now-current events.
|
||||
*/
|
||||
public advance(): void {
|
||||
public advance() {
|
||||
this.time += 1;
|
||||
const currentEvents = this.events[this.time];
|
||||
|
||||
@@ -136,13 +127,9 @@ export class TimeHandlr {
|
||||
* @param event An event to be handled.
|
||||
* @returns A new time the event is scheduled for (or undefined if it isn't).
|
||||
*/
|
||||
public handleEvent(event: TimeEventLike): number | undefined {
|
||||
public handleEvent(event: TimeEvent): number | undefined {
|
||||
// Events return truthy values to indicate a stop.
|
||||
if (
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
event.repeat! <= 0 ||
|
||||
event.callback.apply(this, event.args || [])
|
||||
) {
|
||||
if (event.repeat <= 0 || event.callback.apply(this, event.args || [])) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -172,14 +159,14 @@ export class TimeHandlr {
|
||||
*
|
||||
* @param event Event to cancel.
|
||||
*/
|
||||
public cancelEvent(event: TimeEvent): void {
|
||||
public cancelEvent(event: TimeEvent) {
|
||||
event.repeat = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all events.
|
||||
*/
|
||||
public cancelAllEvents(): void {
|
||||
public cancelAllEvents() {
|
||||
this.events = {};
|
||||
}
|
||||
|
||||
@@ -187,7 +174,7 @@ export class TimeHandlr {
|
||||
* Quick handler to add an event to events at a particular time. If the time
|
||||
* doesn't have any events listed, a new Array is made to hold this event.
|
||||
*/
|
||||
private insertEvent(event: TimeEventLike): void {
|
||||
private insertEvent(event: TimeEvent) {
|
||||
const atTime = this.events[event.time];
|
||||
if (atTime) {
|
||||
atTime.push(event);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { TimeEvent } from "./TimeEvent";
|
||||
|
||||
/**
|
||||
* Lookup of current events, mapping times to all associated events.
|
||||
*/
|
||||
export type CurrentEvents = Record<number, TimeEventLike[] | undefined>;
|
||||
export type CurrentEvents = Record<number, TimeEvent[] | undefined>;
|
||||
|
||||
/**
|
||||
* General-purpose Function for events.
|
||||
@@ -27,48 +29,6 @@ export type NumericCalculator = () => number;
|
||||
*/
|
||||
export type RepeatCalculator = (...args: any[]) => boolean;
|
||||
|
||||
/**
|
||||
* An event to be played, including a callback, repetition settings, and arguments.
|
||||
*/
|
||||
export interface TimeEventLike {
|
||||
/**
|
||||
* The time at which to call this event.
|
||||
*/
|
||||
time: number;
|
||||
|
||||
/**
|
||||
* Something to run when this event is triggered.
|
||||
*/
|
||||
callback(): unknown;
|
||||
|
||||
/**
|
||||
* Arguments to be passed to the callback.
|
||||
*/
|
||||
args?: any[];
|
||||
|
||||
/**
|
||||
* How many times this should repeat. If a Function, called for whether to repeat.
|
||||
*/
|
||||
repeat?: number | RepeatCalculator;
|
||||
|
||||
/**
|
||||
* How long to wait between calls, if repeat isn't 1.
|
||||
*/
|
||||
timeRepeat?: number | NumericCalculator;
|
||||
|
||||
/**
|
||||
* How many times this has been called.
|
||||
*/
|
||||
count?: number;
|
||||
|
||||
/**
|
||||
* Set the next call time using timeRepeat.
|
||||
*
|
||||
* @returns The new call time.
|
||||
*/
|
||||
scheduleNextRepeat(): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings to initialize a new TimeHandlr.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user