Skip to content

Commit

Permalink
Merge pull request #21 from regal/feat/5-delproperty
Browse files Browse the repository at this point in the history
Implemented PropertyOperation.DELETED
  • Loading branch information
jcowman2 committed Aug 14, 2018
2 parents bf6ade2 + 0458779 commit c4a090e
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 23 deletions.
112 changes: 90 additions & 22 deletions src/agent.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import { GameInstance, RegalError, Game } from "./game";
import { EventRecord, on } from "./event";
import { GameInstance, RegalError } from "./game";
import { EventRecord } from "./event";

const StaticAgentProxyHandler = {
get(target: Agent, propertyKey: PropertyKey, receiver: object) {
let value = Reflect.get(target, propertyKey);

if (value === undefined) {
if (
value === undefined
&& (target.game === undefined || !target.game.agents.agentPropertyWasDeleted(target.id, propertyKey))
) {
value = staticAgentRegistry.getAgentProperty(target.id, propertyKey);
}

return value;
},

set(target: Agent, propertyKey: PropertyKey, value: any, receiver: object) {
return Reflect.set(target, propertyKey, value, receiver);
},

has(target: Agent, propertyKey: PropertyKey) {
if (target.game !== undefined && target.game.agents.agentPropertyWasDeleted(target.id, propertyKey)) {
return false;
}
return staticAgentRegistry.hasAgentProperty(target.id, propertyKey);
}
}
};

export class StaticAgentRegistry {

Expand Down Expand Up @@ -63,18 +65,19 @@ export const resetRegistry = () => {
staticAgentRegistry = new StaticAgentRegistry();
};

function isAgent(o: any): o is Agent {
return (<Agent>o).isRegistered !== undefined;
}
export const isAgent = (o: any): o is Agent =>
o && (<Agent>o).isRegistered !== undefined;

const AgentProxyHandler = {
get(target: Agent, propertyKey: PropertyKey, receiver: object): any {
let value: any = undefined;

if (target.isRegistered && propertyKey in receiver) {
// If the property exists in the instance state, return it.
if (target.isRegistered && target.game.agents.hasAgentProperty(target.id, propertyKey)) {
value = target.game.agents.getAgentProperty(target.id, propertyKey);
}
if (value === undefined) {
}
// If the property never existed in the instance state (i.e. wasn't deleted), return the Reflect.get.
else if (target.game === undefined || !target.game.agents.agentPropertyWasDeleted(target.id, propertyKey)) {
value = Reflect.get(target, propertyKey, receiver);
}

Expand All @@ -98,8 +101,21 @@ const AgentProxyHandler = {
return target.isRegistered
? target.game.agents.hasAgentProperty(target.id, propertyKey)
: Reflect.has(target, propertyKey);
},

deleteProperty(target: Agent, propertyKey: PropertyKey): boolean {
let result: boolean = undefined;

if (target.isRegistered && target.game.agents.hasAgentProperty(target.id, propertyKey)) {
const currentEvent = target.game.events.current;
result = target.game.agents.deleteAgentProperty(target.id, propertyKey, currentEvent);
} else {
result = Reflect.deleteProperty(target, propertyKey);
}

return result;
}
}
};

export class Agent {

Expand Down Expand Up @@ -160,9 +176,8 @@ export class Agent {
}
}

function isAgentReference(o: any): o is AgentReference {
return (<AgentReference>o).refId !== undefined;
}
const isAgentReference = (o: any): o is AgentReference =>
o && (<AgentReference>o).refId !== undefined;

export class AgentReference {
constructor(public refId: number) {}
Expand Down Expand Up @@ -255,14 +270,38 @@ export class InstanceAgents {
}

const agentRecord: AgentRecord = this[agentId];
return agentRecord.hasOwnProperty(property);
return agentRecord.hasOwnProperty(property) && !agentRecord.propertyWasDeleted(property);
}

deleteAgentProperty(agentId: number, property: PropertyKey, event: EventRecord): boolean {
if (!this.hasOwnProperty(agentId)) {
if (staticAgentRegistry.hasAgent(agentId)) {
if (staticAgentRegistry.hasAgentProperty(agentId, property)) {
this[agentId] = new AgentRecord();
} else {
// If the static agent doesn't exist in the instance state, but that agent doesn't
// have the property that someone is attempting to delete, we don't do anything.
return false;
}
} else {
throw new RegalError(`No agent with ID <${agentId}> exists in the instance or the static registry.`);
}
}

const agentRecord: AgentRecord = this[agentId];

return agentRecord.deleteProperty(event, agentId, property);
}

agentPropertyWasDeleted(agentId: number, property: PropertyKey): boolean {
return this.hasOwnProperty(agentId) && (<AgentRecord>this[agentId]).propertyWasDeleted(property);
}
}

export enum PropertyOperation {
ADDED = "ADDED",
MODIFIED = "MODIFIED",
DELETED = "DELETED" // TODO: support
DELETED = "DELETED"
}

export interface PropertyChange {
Expand All @@ -276,7 +315,7 @@ export interface PropertyChange {
}

export class AgentRecord {

getProperty(propertyKey: PropertyKey): any {
const changes: PropertyChange[] = this[propertyKey];
let property: any = undefined;
Expand All @@ -296,11 +335,40 @@ export class AgentRecord {
}

const op = initValue === undefined ? PropertyOperation.ADDED : PropertyOperation.MODIFIED;

this._addRecord(event, property, op, initValue, value);

event.trackChange(agentId, property, op, initValue, value);
}

deleteProperty(event: EventRecord, agentId: number, property: PropertyKey): boolean {
let initValue = this.getProperty(property);

if (!this.hasOwnProperty(property)) {
if (staticAgentRegistry.hasAgentProperty(agentId, property)) {
initValue = staticAgentRegistry.getAgentProperty(agentId, property);
} else {
return false;
}
}

this._addRecord(event, property, PropertyOperation.DELETED, initValue);
event.trackChange(agentId, property, PropertyOperation.DELETED, initValue);

return true;
}

propertyWasDeleted(propertyKey: PropertyKey): boolean {
if (this.hasOwnProperty(propertyKey)) {
const lastChange: PropertyChange = this[propertyKey][0];

if (lastChange.op === PropertyOperation.DELETED) {
return true;
}
}

return false;
}

private _addRecord<T>(event: EventRecord, property: PropertyKey, op: PropertyOperation, init?: T, final?: T): void {
if (!(property in this)) {
this[property] = new Array<PropertyChange>();
Expand Down
119 changes: 119 additions & 0 deletions test/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ describe("Agent", function() {
).to.throw(RegalError, "No agent with ID <1> exists in the instance or the static registry.")
});

it("Error check for InstanceAgents#deleteAgentProperty for an unused ID", function() {
expect(() =>
new GameInstance().agents.deleteAgentProperty(1, "foo", EventRecord.default)
).to.throw(RegalError, "No agent with ID <1> exists in the instance or the static registry.")
});

it("Registering an agent registers its property agents as well", function() {
const game = new GameInstance();
let dummy = new Dummy("D1", 10);
Expand Down Expand Up @@ -205,6 +211,31 @@ describe("Agent", function() {
expect("name" in dummy).to.be.true;
expect("foo" in dummy).to.be.false;
});

it("Registered agents' properties may be deleted", function() {
const myGame = new GameInstance();
const dummy = new Dummy("D1", 10).register(myGame);

expect(dummy.name).to.equal("D1");
expect("name" in dummy).to.be.true;

delete dummy.name;

expect(dummy.name).to.be.undefined;
expect("name" in dummy).to.be.false;
});

it("Unregistered agents' properties may be deleted", function() {
const dummy = new Dummy("D1", 10);

expect(dummy.name).to.equal("D1");
expect("name" in dummy).to.be.true;

delete dummy.name;

expect(dummy.name).to.be.undefined;
expect("name" in dummy).to.be.false;
});
});

describe("Static Agents", function() {
Expand Down Expand Up @@ -346,6 +377,26 @@ describe("Agent", function() {
new Dummy("D2", 12).register(myGame, 1)
).to.throw(RegalError, "A static agent already has the ID <1>.")
});

it("A static agent's properties can be deleted", function() {
const myGame = new GameInstance();
const dummy = new Dummy("D1", 10).static().register(myGame);

expect(dummy.name).to.equal("D1");
expect("name" in dummy).to.be.true;

delete dummy.name;

expect(dummy.name).to.be.undefined;
expect("name" in dummy).to.be.false;
});

it("InstanceAgents#deleteAgentProperty will return false if one tries to delete a static agent property that doesn't exist", function() {
const myGame = new GameInstance();
const dummy = new Dummy("D1", 10).static().register(myGame);

expect(myGame.agents.deleteAgentProperty(1, "foo", EventRecord.default)).to.be.false;
});
});

describe("Instance State", function() {
Expand Down Expand Up @@ -488,5 +539,73 @@ describe("Agent", function() {
]
});
});

it("Deleting a registered agent's property adds a record", function() {
const game = new GameInstance();
const dummy = new Dummy("D1", 10).register(game);

delete dummy.name;

expect(game.agents[1].name).to.deep.equal([
{
eventId: 0,
eventName: "DEFAULT",
op: PropertyOperation.DELETED,
init: "D1",
final: undefined
},
{
eventId: 0,
eventName: "DEFAULT",
op: PropertyOperation.ADDED,
init: undefined,
final: "D1"
}
]);
});

it("Deleting a static agent's property adds only that change to the record", function() {
const game = new GameInstance();
const dummy = new Dummy("D1", 10).static().register(game);

delete dummy.name;

expect(game.agents[1]).to.deep.equal({
name: [
{
eventId: 0,
eventName: "DEFAULT",
op: PropertyOperation.DELETED,
init: "D1",
final: undefined
}
]
});
});

it("If you try and delete a nonexistent property of a registered agent, nothing will happen", function() {
const game = new GameInstance();
const dummy = new Dummy("D1", 10).register(game);

delete dummy["foo"];

expect(game.agents[1].foo).to.be.undefined;
});

it("If you try and delete a nonexistent property of a registered static agent, nothing will happen", function() {
const game = new GameInstance();
const dummy = new Dummy("D1", 10).static().register(game);

delete dummy["foo"];

expect(game.agents[1]).to.be.undefined;
});

it("AgentRecord#deleteProperty will return false if one tries to delete a agent property that doesn't exist", function() {
const myGame = new GameInstance();
const dummy = new Dummy("D1", 10).register(myGame);

expect(myGame.agents[1].deleteProperty(EventRecord.default, 1, "foo")).to.be.false;
});
});
});
1 change: 0 additions & 1 deletion test/mocha.opts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
--compilers ts-node/register
--require source-map-support/register
--full-trace
--bail
test/**/*.test.ts

0 comments on commit c4a090e

Please sign in to comment.