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

[DTP-952, DTP-953] Add base implementation for abstract LiveObject, concrete LiveMap/LiveCounter classes #1882

Merged
merged 4 commits into from
Oct 8, 2024
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
7 changes: 6 additions & 1 deletion scripts/moduleReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,12 @@ async function checkLiveObjectsPluginFiles() {
const pluginBundleInfo = getBundleInfo('./build/liveobjects.js');

// These are the files that are allowed to contribute >= `threshold` bytes to the LiveObjects bundle.
const allowedFiles = new Set(['src/plugins/liveobjects/index.ts']);
const allowedFiles = new Set([
'src/plugins/liveobjects/index.ts',
'src/plugins/liveobjects/liveobject.ts',
'src/plugins/liveobjects/liveobjects.ts',
'src/plugins/liveobjects/liveobjectspool.ts',
]);

return checkBundleFiles(pluginBundleInfo, allowedFiles, 100);
}
Expand Down
11 changes: 11 additions & 0 deletions src/plugins/liveobjects/livecounter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { LiveObject } from './liveobject';

export interface LiveCounterData {
data: number;
}

export class LiveCounter extends LiveObject<LiveCounterData> {
protected _getZeroValueData(): LiveCounterData {
return { data: 0 };
}
}
VeskeR marked this conversation as resolved.
Show resolved Hide resolved
34 changes: 34 additions & 0 deletions src/plugins/liveobjects/livemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { LiveObject } from './liveobject';

export type StateValue = string | number | boolean | Uint8Array;

export interface ObjectIdStateData {
/**
* A reference to another state object, used to support composable state objects.
*/
objectId: string;
}

export interface ValueStateData {
/**
* A concrete leaf value in the state object graph.
*/
value: StateValue;
}

export type StateData = ObjectIdStateData | ValueStateData;

export interface MapEntry {
// TODO: add tombstone, timeserial
data: StateData;
}

export interface LiveMapData {
data: Map<string, MapEntry>;
}

export class LiveMap extends LiveObject<LiveMapData> {
protected _getZeroValueData(): LiveMapData {
return { data: new Map<string, MapEntry>() };
}
VeskeR marked this conversation as resolved.
Show resolved Hide resolved
}
33 changes: 33 additions & 0 deletions src/plugins/liveobjects/liveobject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { LiveObjects } from './liveobjects';

interface LiveObjectData {
data: any;
}

export abstract class LiveObject<T extends LiveObjectData = LiveObjectData> {
protected _dataRef: T;
protected _objectId: string;

constructor(
protected _liveObjects: LiveObjects,
initialData?: T | null,
objectId?: string,
) {
this._dataRef = initialData ?? this._getZeroValueData();
this._objectId = objectId ?? this._createObjectId();
}

/**
* @internal
*/
getObjectId(): string {
return this._objectId;
}

private _createObjectId(): string {
// TODO: implement object id generation based on live object type and initial value
return Math.random().toString().substring(2);
VeskeR marked this conversation as resolved.
Show resolved Hide resolved
}

protected abstract _getZeroValueData(): T;
}
9 changes: 9 additions & 0 deletions src/plugins/liveobjects/liveobjects.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import type BaseClient from 'common/lib/client/baseclient';
import type RealtimeChannel from 'common/lib/client/realtimechannel';
import { LiveMap } from './livemap';
import { LiveObjectsPool, ROOT_OBJECT_ID } from './liveobjectspool';

export class LiveObjects {
private _client: BaseClient;
private _channel: RealtimeChannel;
private _liveObjectsPool: LiveObjectsPool;

constructor(channel: RealtimeChannel) {
this._channel = channel;
this._client = channel.client;
this._liveObjectsPool = new LiveObjectsPool(this);
}
VeskeR marked this conversation as resolved.
Show resolved Hide resolved

async getRoot(): Promise<LiveMap> {
// TODO: wait for SYNC sequence to finish to return root
return this._liveObjectsPool.get(ROOT_OBJECT_ID) as LiveMap;
}
}
25 changes: 25 additions & 0 deletions src/plugins/liveobjects/liveobjectspool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { LiveMap } from './livemap';
import { LiveObject } from './liveobject';
import { LiveObjects } from './liveobjects';

export type ObjectId = string;
export const ROOT_OBJECT_ID = 'root';

export class LiveObjectsPool {
private _pool: Map<ObjectId, LiveObject>;

constructor(private _liveObjects: LiveObjects) {
this._pool = this._getInitialPool();
}

get(objectId: ObjectId): LiveObject | undefined {
return this._pool.get(objectId);
}

private _getInitialPool(): Map<ObjectId, LiveObject> {
const pool = new Map<ObjectId, LiveObject>();
const root = new LiveMap(this._liveObjects, null, ROOT_OBJECT_ID);
pool.set(root.getObjectId(), root);
return pool;
}
}
1 change: 1 addition & 0 deletions test/common/modules/private_api_recorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ define(['test/support/output_directory_paths'], function (outputDirectoryPaths)
'call.http._getHosts',
'call.http.checkConnectivity',
'call.http.doUri',
'call.LiveObject.getObjectId',
'call.msgpack.decode',
'call.msgpack.encode',
'call.presence._myMembers.put',
Expand Down
31 changes: 31 additions & 0 deletions test/realtime/live_objects.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,37 @@ define(['ably', 'shared_helper', 'async', 'chai', 'live_objects'], function (
const channel = client.channels.get('channel');
expect(channel.liveObjects.constructor.name).to.equal('LiveObjects');
});

describe('LiveObjects instance', () => {
/** @nospec */
it('getRoot() returns LiveMap instance', async function () {
const helper = this.test.helper;
const client = LiveObjectsRealtime(helper);

await helper.monitorConnectionThenCloseAndFinish(async () => {
const channel = client.channels.get('channel');
const liveObjects = channel.liveObjects;
const root = await liveObjects.getRoot();

expect(root.constructor.name).to.equal('LiveMap');
}, client);
});

/** @nospec */
it('getRoot() returns live object with id "root"', async function () {
const helper = this.test.helper;
const client = LiveObjectsRealtime(helper);

await helper.monitorConnectionThenCloseAndFinish(async () => {
const channel = client.channels.get('channel');
const liveObjects = channel.liveObjects;
const root = await liveObjects.getRoot();

helper.recordPrivateApi('call.LiveObject.getObjectId');
expect(root.getObjectId()).to.equal('root');
}, client);
});
});
});
});
});
Loading