diff --git a/src/state/Scene.ts b/src/state/Scene.ts index 2e7d021..e1153f8 100644 --- a/src/state/Scene.ts +++ b/src/state/Scene.ts @@ -82,7 +82,7 @@ export class Scene extends EventEmitter implements IScene { private onControlUpdated(controlData: IControlData) { const control = this.getControl(controlData.controlID); if (control) { - control.update(controlData); + control.onUpdate(controlData); } } /** diff --git a/src/state/controls/Button.ts b/src/state/controls/Button.ts index b3e2209..d7c8386 100644 --- a/src/state/controls/Button.ts +++ b/src/state/controls/Button.ts @@ -1,4 +1,4 @@ -import { IButton, IButtonData } from '../interfaces/controls/IButton'; +import { IButton, IButtonData, IButtonUpdate } from '../interfaces/controls/IButton'; import { IButtonInput } from '../interfaces/controls/IInput'; import { Control } from './Control'; @@ -71,4 +71,16 @@ export class Button extends Control implements IButton { public giveInput(input: IButtonInput): Promise { return this.sendInput(input); } + + /** + * Update this button on the server. + */ + public update(controlUpdate: IButtonUpdate): Promise { + // Clone to prevent mutations + const changedData = {...controlUpdate}; + if (changedData.cooldown) { + changedData.cooldown = this.client.state.synchronizeLocalTime().getTime() + changedData.cooldown; + } + return super.update(changedData); + } } diff --git a/src/state/controls/Control.spec.ts b/src/state/controls/Control.spec.ts index 401f17d..6c83a35 100644 --- a/src/state/controls/Control.spec.ts +++ b/src/state/controls/Control.spec.ts @@ -2,6 +2,7 @@ import { expect, use } from 'chai'; import * as sinon from 'sinon'; import { Client, ClientType } from '../../Client'; +import { IButtonUpdate } from '../interfaces/controls'; import { Scene } from '../Scene'; import { Button } from './'; @@ -58,4 +59,24 @@ describe('control', () => { }); stub.restore(); }); + + it('allows batch updates', () => { + const buttonDiff: IButtonUpdate = { + cost: 200, + text: 'foobar', + }; + const updatedButton = { + etag: buttonData.etag, + controlID: buttonData.controlID, + ...buttonDiff, + }; + const stub = sinon.stub(mockClient, 'updateControls'); + control.update(buttonDiff); + expect(stub).to.be + .calledWith({ + sceneID: 'default', + controls: [updatedButton], + }); + stub.restore(); + }); }); diff --git a/src/state/controls/Control.ts b/src/state/controls/Control.ts index 153336d..906f74c 100644 --- a/src/state/controls/Control.ts +++ b/src/state/controls/Control.ts @@ -7,6 +7,7 @@ import { ControlKind, IControl, IControlData, + IControlUpdate, IGridPlacement, } from '../interfaces/controls/IControl'; import { IInput, IInputEvent } from '../interfaces/controls/IInput'; @@ -93,13 +94,29 @@ export abstract class Control extends EventEmitter imple } /** - * Merges in values from the server in response to an update operation. + * Merges in values from the server in response to an update operation from the server. */ - public update(controlData: IControlData) { + public onUpdate(controlData: IControlData) { merge(this, controlData); this.emit('updated', this); } + /** + * Update this control on the server. + */ + public update(controlUpdate: T2): Promise { + const changedData = { + ...controlUpdate, + controlID: this.controlID, + etag: this.etag, + }; + + return this.client.updateControls({ + sceneID: this.scene.sceneID, + controls: [changedData], + }); + } + public destroy(): void { this.emit('deleted', this); } diff --git a/src/state/interfaces/controls/IButton.ts b/src/state/interfaces/controls/IButton.ts index 4d89b8d..d66a281 100644 --- a/src/state/interfaces/controls/IButton.ts +++ b/src/state/interfaces/controls/IButton.ts @@ -1,5 +1,5 @@ import { IParticipant } from '../'; -import { IControl, IControlData } from './IControl'; +import { IControl, IControlData, IControlUpdate } from './IControl'; import { IButtonInput, IInputEvent } from './IInput'; /** @@ -28,6 +28,33 @@ export interface IButtonData extends IControlData { keyCode?: number; } +/** + * Represents updatable components of a button which developers can update + * from game clients. + */ +export interface IButtonUpdate extends IControlUpdate { + /** + * Will update the text of this button. + */ + text?: string; + /** + * In milliseconds, will be converted to a unix timestamp of when this cooldown expires. + */ + cooldown?: number; + /** + * Will update the spark cost of this button. + */ + cost?: number; + /** + * Will update the progress bar underneath the button. 0 - 1. + */ + progress?: number; + /** + * Will update the keycode used by participants for keyboard control. + */ + keyCode?: number; +} + export interface IButton extends IControl, IButtonData { text: string; cost: number; @@ -39,6 +66,7 @@ export interface IButton extends IControl, IButtonData { setProgress(progress: number): Promise; setCooldown(duration: number): Promise; setCost(cost: number): Promise; + update(changedData: IButtonUpdate): Promise; /** * Fired when a participant presses this button. diff --git a/src/state/interfaces/controls/IControl.ts b/src/state/interfaces/controls/IControl.ts index 19f41b0..b1b9b7d 100644 --- a/src/state/interfaces/controls/IControl.ts +++ b/src/state/interfaces/controls/IControl.ts @@ -2,7 +2,6 @@ import { EventEmitter } from 'events'; import { ETag, IParticipant } from '../'; import { IClient } from '../../../IClient'; -import { ISceneDataArray } from '../IScene'; import { IInput, IInputEvent } from './IInput'; import { IMeta } from './IMeta'; @@ -40,6 +39,19 @@ export interface IControlData { */ etag?: ETag; } + +/** + * Represents updatable components of a control which developers can update + * from game clients. + */ +export interface IControlUpdate { + /** + * When set to true this will disable the control. + * When set to false this will enable the control. + */ + disabled?: boolean; +} + /** * Control is used a base class for all other controls within an interactive session. * It contains shared logic which all types of controls can utilize. @@ -69,7 +81,12 @@ export interface IControl extends IControlData, EventEmitter { /** * Merges in updated control data from the mediator */ - update(controlData: IControlData): void; + onUpdate(controlData: IControlData): void; + + /** + * Updates the control with the supplied update parameters + */ + update(controlUpdate: IControlUpdate): Promise; /** * Fired when the control is deleted. @@ -113,7 +130,3 @@ export interface IGridPlacement { */ y: number; } - -export interface IControlUpdate { - scenes: ISceneDataArray; -} diff --git a/src/state/interfaces/controls/IJoystick.ts b/src/state/interfaces/controls/IJoystick.ts index b19fb9f..76e49d0 100644 --- a/src/state/interfaces/controls/IJoystick.ts +++ b/src/state/interfaces/controls/IJoystick.ts @@ -1,5 +1,5 @@ import { IParticipant } from '../'; -import { IControl, IControlData } from './IControl'; +import { IControl, IControlData, IControlUpdate } from './IControl'; import { IInputEvent, IJoystickInput } from './IInput'; /** @@ -8,7 +8,7 @@ import { IInputEvent, IJoystickInput } from './IInput'; export interface IJoystickData extends IControlData { /** * The angle of the Joysticks direction indicator. - * In radians 0 - 2. + * In radians 0 - 2π. */ angle?: number; /** @@ -24,6 +24,28 @@ export interface IJoystickData extends IControlData { sampleRate?: number; } +/** + * Represents updatable components of a joystick which developers can update + * from game clients. + */ +export interface IJoystickUpdate extends IControlUpdate { + /** + * Updates the angle of the Joysticks direction indicator. + * In radians 0 - 2π. + */ + angle?: number; + /** + * updates the strength/opacity of the direction indicator. + */ + intensity?: number; + /** + * Updates the sampleRate of this joystick + * + * In milliseconds. + */ + sampleRate?: number; +} + /** * A joysticks coordinates. * @@ -48,6 +70,11 @@ export interface IJoystick extends IControl, IJoystickData { */ setIntensity(intensity: number): Promise; + /** + * Updates the joystick with the supplied joystick parameters + */ + update(controlUpdate: IJoystickUpdate): Promise; + /** * Fired when a participant moves this joystick. */