Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype Registry: Implement primary key system #106

Merged
merged 5 commits into from
Jun 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ dist*
coverage
.nyc_output
node_modules
.rpt2_cache
.rpt2_cache
.DS_Store
16 changes: 14 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,26 @@
"type": "node",
"request": "launch",
"name": "Mocha All",
"program": "${workspaceFolder}\\node_modules\\mocha\\bin\\_mocha",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"-r",
"ts-node/register",
"--timeout",
"999999",
"--colors",
"${workspaceFolder}\\**\\test\\**\\*.test.ts"
"${workspaceFolder}/**/test/**/*.test.ts"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"protocol": "inspector"
},
{
"type": "node",
"request": "launch",
"name": "TS Node Playground",
"program": "${workspaceFolder}/node_modules/.bin/ts-node",
"args": [
"${workspaceFolder}/_playground/pg.ts"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
Expand Down
19 changes: 19 additions & 0 deletions src/common/impl/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* The purpose of this file is to abstract all common utility implementations
* by re-exporting their constructors from a single file.
*
* Copyright (c) Joseph R Cowman
* Licensed under MIT License (see https://github.com/regal/regal)
*/

import { PK, PKProvider, ReservedPKSet } from "../keys";
import { NumericPKImpl } from "./pk-impl";
import { PKProviderImpl } from "./pk-provider-impl";

/** Builds a `PK`. */
export const buildPK = <T>(value: number): PK<T> => new NumericPKImpl(value);

/** Builds a `PKProvider`. */
export const buildPKProvider = <T>(
reserved?: ReservedPKSet<T>
): PKProvider<T> => PKProviderImpl.build(buildPK, reserved);
35 changes: 35 additions & 0 deletions src/common/impl/pk-impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Contains the current implementation of `PK`.
*
* Copyright (c) Joseph R Cowman
* Licensed under MIT License (see https://github.com/regal/regal)
*/

import { FK, PK } from "../keys";

/**
* Current implementation of `PK`. Uses a numeric key.
*/
export class NumericPKImpl<T> implements PK<T> {
constructor(private internalValue: number) {}

public plus(n: number): PK<T> {
return new NumericPKImpl(this.internalValue + n);
}

public minus(n: number): PK<T> {
return new NumericPKImpl(this.internalValue - n);
}

public equals(key: PK<T> | FK<T>): boolean {
return this === key || this.value() === key.value();
}

public ref(): FK<T> {
return this;
}

public value(): string {
return String(this.internalValue);
}
}
78 changes: 78 additions & 0 deletions src/common/impl/pk-provider-impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Contains the current implementation of `PKProvider`.
*
* Copyright (c) Joseph R Cowman
* Licensed under MIT License (see https://github.com/regal/regal)
*/

import { RegalError } from "../../error";
import { PK, PKProvider, ReservedPKSet } from "../keys";

export class PKProviderImpl<PKClass> implements PKProvider<PKClass> {
public static readonly START_VALUE = 0;

/**
* Builds a `PKProvider`.
* @param buildPK The abstracted `PK` build function. Assumes that `PK` accepts a number for its internal value.
* @param reservedKeys Any reserved keys.
*/
public static build<T>(
buildPK: (internalValue: number) => PK<T>,
reservedKeys: ReservedPKSet<T> = {}
): PKProviderImpl<T> {
let lastPK = buildPK(PKProviderImpl.START_VALUE);

const sortedValues = Object.keys(reservedKeys)
.map(key => reservedKeys[key])
.sort();

if (sortedValues.length === 0) {
return new PKProviderImpl(lastPK);
}

const reservedPKs = {};

const min = sortedValues[0];
const max = sortedValues[sortedValues.length - 1];
const range = max - min;

if (range !== sortedValues.length - 1) {
throw new RegalError(
"reservedKeys must be a continuous range of numbers, with no missing or duplicate values."
);
}

reservedPKs[sortedValues[0]] = lastPK;

for (let i = 1; i < sortedValues.length; i++) {
lastPK = lastPK.plus(1);
reservedPKs[sortedValues[i]] = lastPK;
}

return new PKProviderImpl(lastPK, reservedPKs);
}

constructor(
private lastPK: PK<PKClass>,
private reservedPKs: { [key: string]: PK<PKClass> } = {}
) {}

public next(): PK<PKClass> {
this.lastPK = this.lastPK.plus(1);
return this.lastPK;
}

public fork(): PKProvider<PKClass> {
return new PKProviderImpl(this.lastPK, this.reservedPKs);
}

public reserved(key: number): PK<PKClass> {
const pk = this.reservedPKs[key];
if (!pk) {
throw new RegalError(
`No reserved PK exists with the reserved key ${key}.`
);
}
return pk;
}
}
9 changes: 9 additions & 0 deletions src/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Common utilities used by multiple components in the Regal Game Library.
*
* Copyright (c) Joseph R Cowman
* Licensed under MIT License (see https://github.com/regal/regal)
*/

export { PK, FK, ReservedPKSet, PKProvider } from "./keys";
export { buildPKProvider } from "./impl";
89 changes: 89 additions & 0 deletions src/common/keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Contains interfaces for the Game Library's primary key system.
*
* Copyright (c) Joseph R Cowman
* Licensed under MIT License (see https://github.com/regal/regal)
*/

/**
* Primary key for an indexed class, a class of which there
* are many instances that each need a unique identifier.
*
* @template T The class referenced by this primary key type - i.e. `PK<Agent>`
*/
export interface PK<T> {
/**
* Generates the primary key that would be generated `n` keys
* after this one. The result of this function should never be
* used to assign a key to an object. It's only for comparison.
*/
plus(n: number): PK<T>;

/**
* Generates the primary key that would be generated `n` keys
* before this one. The result of this function should never be
* used to assign a key to an object. It's only for comparison.
*/
minus(n: number): PK<T>;

/** Whether this key is equivalent to the given one. */
equals(key: PK<T> | FK<T>): boolean;

/** Generates a foreign key that references this key. */
ref(): FK<T>;

/**
* Generates a string value representative of this key.
* This is used for the `equals` method, which is strongly preferred
* for testing the equality of two keys.
*/
value(): string;
}

/**
* A set of reserved keys for an indexed class. The values of the keys
* in a set don't need to be in order, but the values must make up a
* continuous range of numbers with no missing or duplicate elements.
*
* @template T The class referenced by this primary key type.
*/
export interface ReservedPKSet<T> {
[key: string]: number;
}

/**
* A generator for primary keys. The provider is the single source
* of truth for all primary keys of a given class. Requesting keys
* from a `PKProvider` is the only way to ensure no duplicate keys
* are generated by mistake.
*
* @template T The class of primary key provided by this provider.
*/
export interface PKProvider<T> {
/** Generates a unique `PK`. */
next(): PK<T>;

/**
* Creates a copy of the `PKProvider`. The new provider maintains
* the same references to any reserved keys and the keys generated
* up to this point, but after that the link is severed. If you
* continue generating keys from both the old and new providers, there
* will almost certainly be duplicates.
*/
fork(): PKProvider<T>;

/**
* Retrieves the primary key associated with an entry of the reserved
* set that was used to create the provider. It's best to compare reserved
* keys generated through this method rather than comparing entries in the
* original set, as this method performs validation.
*/
reserved(key: number): PK<T>;
}

/**
* A foreign key, which is a reference to another object's primary key.
*
* @template T The class referenced by this foreign key type.
*/
export interface FK<T> extends PK<T> {}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ export { GameOptions, GameMetadata, InstanceOptions } from "./config";
export { GameInstance } from "./state";
export { OutputLineType, OutputLine, InstanceOutput } from "./output";
export { InstanceRandom, Charsets } from "./random";
export { FK, PK } from "./common";
25 changes: 25 additions & 0 deletions test/unit/agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1563,6 +1563,31 @@ describe("Agents", function() {
);
});
});

describe("Methods and Inheritance", function() {
class ArrayGetterAgent extends Agent {
public dummy1 = new Dummy("D1", 10);
public dummy2 = new Dummy("D2", 20);

public getList() {
return [this.dummy1, this.dummy2];
}
}

it.skip("An agent subclass may have a method which returns an array of its properties, which are also agents", function() {
Game.init(MD);

const myGame = buildGameInstance();
const ag = myGame.using(new ArrayGetterAgent());

const props = [];
for (const d of ag.getList()) {
props.push(d.name);
}

expect(props).to.deep.equal(["D1", "D2"]);
});
});
});

describe("Agent Managers", function() {
Expand Down
Loading