Skip to content

Commit

Permalink
Merge pull request #1917 from ably/liveobjects/subscriptions
Browse files Browse the repository at this point in the history
[DTP-958, DTP-959] Liveobjects/subscriptions
  • Loading branch information
VeskeR authored Nov 14, 2024
2 parents ec59c04 + 4bca1c7 commit 928dea7
Show file tree
Hide file tree
Showing 11 changed files with 781 additions and 158 deletions.
1 change: 1 addition & 0 deletions grunt/esbuild/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const liveObjectsPluginConfig = {
entryPoints: ['src/plugins/liveobjects/index.ts'],
plugins: [umdWrapper.default({ libraryName: 'AblyLiveObjectsPlugin', amdNamedModule: false })],
outfile: 'build/liveobjects.js',
external: ['deep-equal'],
};

const liveObjectsPluginCdnConfig = {
Expand Down
144 changes: 30 additions & 114 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
],
"dependencies": {
"@ably/msgpack-js": "^0.4.0",
"deep-equal": "^2.2.3",
"fastestsmallesttextencoderdecoder": "^1.0.22",
"got": "^11.8.5",
"ulid": "^2.3.0",
Expand All @@ -72,6 +73,7 @@
"@babel/traverse": "^7.23.7",
"@testing-library/react": "^13.3.0",
"@types/cli-table": "^0.3.4",
"@types/deep-equal": "^1.0.4",
"@types/jmespath": "^0.15.2",
"@types/node": "^18.0.0",
"@types/request": "^2.48.7",
Expand Down
30 changes: 22 additions & 8 deletions scripts/moduleReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ const functions = [
{ name: 'constructPresenceMessage', transitiveImports: [] },
];

// List of all buildable plugins available as a separate export
interface PluginInfo {
description: string;
path: string;
external?: string[];
}

const buildablePlugins: Record<'push' | 'liveObjects', PluginInfo> = {
push: { description: 'Push', path: './build/push.js', external: ['ulid'] },
liveObjects: { description: 'LiveObjects', path: './build/liveobjects.js', external: ['deep-equal'] },
};

function formatBytes(bytes: number) {
const kibibytes = bytes / 1024;
const formatted = kibibytes.toFixed(2);
Expand Down Expand Up @@ -70,7 +82,7 @@ function getModularBundleInfo(exports: string[]): BundleInfo {
}

// Uses esbuild to create a bundle containing the named exports from a given module
function getBundleInfo(modulePath: string, exports?: string[]): BundleInfo {
function getBundleInfo(modulePath: string, exports?: string[], external?: string[]): BundleInfo {
const outfile = exports ? exports.join('') : 'all';
const exportTarget = exports ? `{ ${exports.join(', ')} }` : '*';
const result = esbuild.buildSync({
Expand All @@ -84,7 +96,7 @@ function getBundleInfo(modulePath: string, exports?: string[]): BundleInfo {
outfile,
write: false,
sourcemap: 'external',
external: ['ulid'],
external,
});

const pathHasBase = (component: string) => {
Expand Down Expand Up @@ -183,9 +195,9 @@ async function calculateAndCheckFunctionSizes(): Promise<Output> {
return output;
}

async function calculatePluginSize(options: { path: string; description: string }): Promise<Output> {
async function calculatePluginSize(options: PluginInfo): Promise<Output> {
const output: Output = { tableRows: [], errors: [] };
const pluginBundleInfo = getBundleInfo(options.path);
const pluginBundleInfo = getBundleInfo(options.path, undefined, options.external);
const sizes = {
rawByteSize: pluginBundleInfo.byteSize,
gzipEncodedByteSize: (await promisify(gzip)(pluginBundleInfo.code)).byteLength,
Expand All @@ -200,11 +212,11 @@ async function calculatePluginSize(options: { path: string; description: string
}

async function calculatePushPluginSize(): Promise<Output> {
return calculatePluginSize({ path: './build/push.js', description: 'Push' });
return calculatePluginSize(buildablePlugins.push);
}

async function calculateLiveObjectsPluginSize(): Promise<Output> {
return calculatePluginSize({ path: './build/liveobjects.js', description: 'LiveObjects' });
return calculatePluginSize(buildablePlugins.liveObjects);
}

async function calculateAndCheckMinimalUsefulRealtimeBundleSize(): Promise<Output> {
Expand Down Expand Up @@ -291,7 +303,8 @@ async function checkBaseRealtimeFiles() {
}

async function checkPushPluginFiles() {
const pushPluginBundleInfo = getBundleInfo('./build/push.js');
const { path, external } = buildablePlugins.push;
const pushPluginBundleInfo = getBundleInfo(path, undefined, external);

// These are the files that are allowed to contribute >= `threshold` bytes to the Push bundle.
const allowedFiles = new Set([
Expand All @@ -305,7 +318,8 @@ async function checkPushPluginFiles() {
}

async function checkLiveObjectsPluginFiles() {
const pluginBundleInfo = getBundleInfo('./build/liveobjects.js');
const { path, external } = buildablePlugins.liveObjects;
const pluginBundleInfo = getBundleInfo(path, undefined, external);

// These are the files that are allowed to contribute >= `threshold` bytes to the LiveObjects bundle.
const allowedFiles = new Set([
Expand Down
32 changes: 24 additions & 8 deletions src/plugins/liveobjects/livecounter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LiveObject, LiveObjectData } from './liveobject';
import { LiveObject, LiveObjectData, LiveObjectUpdate, LiveObjectUpdateNoop } from './liveobject';
import { LiveObjects } from './liveobjects';
import { StateCounter, StateCounterOp, StateMessage, StateOperation, StateOperationAction } from './statemessage';
import { Timeserial } from './timeserial';
Expand All @@ -7,7 +7,11 @@ export interface LiveCounterData extends LiveObjectData {
data: number;
}

export class LiveCounter extends LiveObject<LiveCounterData> {
export interface LiveCounterUpdate extends LiveObjectUpdate {
update: { inc: number };
}

export class LiveCounter extends LiveObject<LiveCounterData, LiveCounterUpdate> {
constructor(
liveObjects: LiveObjects,
private _created: boolean,
Expand Down Expand Up @@ -62,16 +66,19 @@ export class LiveCounter extends LiveObject<LiveCounterData> {
);
}

let update: LiveCounterUpdate | LiveObjectUpdateNoop;
switch (op.action) {
case StateOperationAction.COUNTER_CREATE:
this._applyCounterCreate(op.counter);
update = this._applyCounterCreate(op.counter);
break;

case StateOperationAction.COUNTER_INC:
if (this._client.Utils.isNil(op.counterOp)) {
this._throwNoPayloadError(op);
// leave an explicit return here, so that TS knows that update object is always set after the switch statement.
return;
} else {
this._applyCounterInc(op.counterOp);
update = this._applyCounterInc(op.counterOp);
}
break;

Expand All @@ -84,12 +91,18 @@ export class LiveCounter extends LiveObject<LiveCounterData> {
}

this.setRegionalTimeserial(opRegionalTimeserial);
this.notifyUpdated(update);
}

protected _getZeroValueData(): LiveCounterData {
return { data: 0 };
}

protected _updateFromDataDiff(currentDataRef: LiveCounterData, newDataRef: LiveCounterData): LiveCounterUpdate {
const counterDiff = newDataRef.data - currentDataRef.data;
return { update: { inc: counterDiff } };
}

private _throwNoPayloadError(op: StateOperation): void {
throw new this._client.ErrorInfo(
`No payload found for ${op.action} op for LiveCounter objectId=${this.getObjectId()}`,
Expand All @@ -98,7 +111,7 @@ export class LiveCounter extends LiveObject<LiveCounterData> {
);
}

private _applyCounterCreate(op: StateCounter | undefined): void {
private _applyCounterCreate(op: StateCounter | undefined): LiveCounterUpdate | LiveObjectUpdateNoop {
if (this.isCreated()) {
// skip COUNTER_CREATE op if this counter is already created
this._client.Logger.logAction(
Expand All @@ -107,24 +120,27 @@ export class LiveCounter extends LiveObject<LiveCounterData> {
'LiveCounter._applyCounterCreate()',
`skipping applying COUNTER_CREATE op on a counter instance as it is already created; objectId=${this._objectId}`,
);
return;
return { noop: true };
}

if (this._client.Utils.isNil(op)) {
// if a counter object is missing for the COUNTER_CREATE op, the initial value is implicitly 0 in this case.
// we need to SUM the initial value to the current value due to the reasons below, but since it's a 0, we can skip addition operation
this.setCreated(true);
return;
return { update: { inc: 0 } };
}

// note that it is intentional to SUM the incoming count from the create op.
// if we get here, it means that current counter instance wasn't initialized from the COUNTER_CREATE op,
// so it is missing the initial value that we're going to add now.
this._dataRef.data += op.count ?? 0;
this.setCreated(true);

return { update: { inc: op.count ?? 0 } };
}

private _applyCounterInc(op: StateCounterOp): void {
private _applyCounterInc(op: StateCounterOp): LiveCounterUpdate {
this._dataRef.data += op.amount;
return { update: { inc: op.amount } };
}
}
Loading

0 comments on commit 928dea7

Please sign in to comment.