Skip to content

Commit dd93dc8

Browse files
Improve flush logic
1 parent 3b3af83 commit dd93dc8

File tree

7 files changed

+214
-84
lines changed

7 files changed

+214
-84
lines changed

packages/app/src/app/overmind/effects/live/clients.ts

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Client } from 'ot';
1+
import { Client, TextOperation } from 'ot';
22

33
export type SendOperation = (
44
moduleShortid: string,
@@ -8,24 +8,11 @@ export type SendOperation = (
88

99
export type ApplyOperation = (moduleShortid: string, operation: any) => void;
1010

11-
function operationToElixir(ot) {
12-
return ot.map(op => {
13-
if (typeof op === 'number') {
14-
if (op < 0) {
15-
return { d: -op };
16-
}
17-
18-
return op;
19-
}
20-
21-
return { i: op };
22-
});
23-
}
24-
2511
class CodeSandboxOTClient extends Client {
2612
moduleShortid: string;
2713
onSendOperation: (revision: string, operation: any) => void;
2814
onApplyOperation: (operation: any) => void;
15+
saveOperation: TextOperation;
2916

3017
constructor(
3118
revision: number,
@@ -39,28 +26,40 @@ class CodeSandboxOTClient extends Client {
3926
this.onApplyOperation = onApplyOperation;
4027
}
4128

42-
sendOperation(revision, operation) {
43-
this.onSendOperation(revision, operationToElixir(operation.toJSON()));
44-
}
29+
flush() {
30+
const saveOperation = this.saveOperation;
4531

46-
applyOperation(operation) {
47-
this.onApplyOperation(operation);
32+
this.saveOperation = null;
33+
34+
return {
35+
revision: this.revision,
36+
operation: saveOperation,
37+
};
4838
}
4939

50-
serverAck() {
51-
super.serverAck();
40+
revertFlush(operation) {
41+
if (this.saveOperation) {
42+
this.saveOperation = operation.compose(this.saveOperation);
43+
} else {
44+
this.saveOperation = operation;
45+
}
5246
}
5347

54-
applyClient(operation: any) {
55-
super.applyClient(operation);
48+
sendOperation(revision, operation) {
49+
this.onSendOperation(revision, operation);
5650
}
5751

58-
applyServer(operation: any) {
59-
super.applyServer(operation);
52+
applyOperation(operation) {
53+
this.onApplyOperation(operation);
6054
}
6155

62-
serverReconnect() {
63-
super.serverReconnect();
56+
applyClient(operation: any) {
57+
if (this.saveOperation) {
58+
this.saveOperation = this.saveOperation.compose(operation);
59+
} else {
60+
this.saveOperation = operation;
61+
}
62+
super.applyClient(operation);
6463
}
6564
}
6665

packages/app/src/app/overmind/effects/live/index.ts

Lines changed: 81 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ import {
77
import _debug from '@codesandbox/common/lib/utils/debug';
88
import { camelizeKeys } from 'humps';
99
import { TextOperation } from 'ot';
10-
import { Socket, Channel } from 'phoenix';
10+
import { Channel, Socket } from 'phoenix';
1111
import uuid from 'uuid';
1212

13-
import clientsFactory from './clients';
1413
import { OPTIMISTIC_ID_PREFIX } from '../utils';
14+
import clientsFactory from './clients';
1515

1616
type Options = {
1717
onApplyOperation(args: { moduleShortid: string; operation: any }): void;
1818
provideJwtToken(): string;
19+
getConnectionsCount(): number;
1920
};
2021

2122
type JoinChannelResponse = {
@@ -29,27 +30,22 @@ declare global {
2930
}
3031
}
3132

32-
const identifier = uuid.v4();
33-
const sentMessages = new Map();
34-
const debug = _debug('cs:socket');
33+
class Live {
34+
private identifier = uuid.v4();
35+
private sentMessages = new Map();
36+
private debug = _debug('cs:socket');
37+
private channel: Channel | null;
38+
private messageIndex = 0;
39+
private clients: ReturnType<typeof clientsFactory>;
40+
private _socket: Socket;
3541

36-
let channel: Channel | null;
37-
let messageIndex = 0;
38-
let clients: ReturnType<typeof clientsFactory>;
39-
let _socket: Socket;
40-
let provideJwtToken: () => string;
42+
private provideJwtToken: () => string;
43+
private getConnectionsCount: () => number;
4144

42-
export default new (class Live {
4345
initialize(options: Options) {
44-
const live = this;
45-
46-
clients = clientsFactory(
46+
this.clients = clientsFactory(
4747
(moduleShortid, revision, operation) => {
48-
live.send('operation', {
49-
moduleShortid,
50-
operation,
51-
revision,
52-
});
48+
this.sendOperation(moduleShortid, revision, operation);
5349
},
5450
(moduleShortid, operation) => {
5551
options.onApplyOperation({
@@ -58,48 +54,49 @@ export default new (class Live {
5854
});
5955
}
6056
);
61-
provideJwtToken = options.provideJwtToken;
57+
this.provideJwtToken = options.provideJwtToken;
58+
this.getConnectionsCount = options.getConnectionsCount;
6259
}
6360

6461
getSocket() {
65-
return _socket || this.connect();
62+
return this._socket || this.connect();
6663
}
6764

6865
connect(): Socket {
69-
if (!_socket) {
66+
if (!this._socket) {
7067
const protocol = process.env.LOCAL_SERVER ? 'ws' : 'wss';
71-
_socket = new Socket(`${protocol}://${location.host}/socket`, {
68+
this._socket = new Socket(`${protocol}://${location.host}/socket`, {
7269
params: {
73-
guardian_token: provideJwtToken(),
70+
guardian_token: this.provideJwtToken(),
7471
},
7572
});
7673

77-
_socket.connect();
78-
window.socket = _socket;
79-
debug('Connecting to socket', _socket);
74+
this._socket.connect();
75+
window.socket = this._socket;
76+
this.debug('Connecting to socket', this._socket);
8077
}
8178

82-
return _socket;
79+
return this._socket;
8380
}
8481

8582
disconnect() {
8683
return new Promise((resolve, reject) => {
87-
if (!channel) {
84+
if (!this.channel) {
8885
resolve({});
8986
return;
9087
}
9188

92-
channel
89+
this.channel
9390
.leave()
9491
.receive('ok', resp => {
95-
if (!channel) {
92+
if (!this.channel) {
9693
return resolve({});
9794
}
9895

99-
channel.onMessage = d => d;
100-
channel = null;
101-
sentMessages.clear();
102-
messageIndex = 0;
96+
this.channel.onMessage = d => d;
97+
this.channel = null;
98+
this.sentMessages.clear();
99+
this.messageIndex = 0;
103100

104101
return resolve(resp);
105102
})
@@ -110,9 +107,9 @@ export default new (class Live {
110107

111108
joinChannel(roomId: string): Promise<JoinChannelResponse> {
112109
return new Promise((resolve, reject) => {
113-
channel = this.getSocket().channel(`live:${roomId}`, { version: 2 });
110+
this.channel = this.getSocket().channel(`live:${roomId}`, { version: 2 });
114111

115-
channel
112+
this.channel
116113
.join()
117114
.receive('ok', resp => {
118115
const result = camelizeKeys(resp) as JoinChannelResponse;
@@ -130,18 +127,18 @@ export default new (class Live {
130127
data: object;
131128
}) => {}
132129
) {
133-
if (!channel) {
130+
if (!this.channel) {
134131
return;
135132
}
136133

137-
channel.onMessage = (event: any, data: any) => {
134+
this.channel.onMessage = (event: any, data: any) => {
138135
const disconnected =
139136
(data == null || Object.keys(data).length === 0) &&
140137
event === 'phx_error';
141138
const alteredEvent = disconnected ? 'connection-loss' : event;
142139

143140
const _isOwnMessage = Boolean(
144-
data && data._messageId && sentMessages.delete(data._messageId)
141+
data && data._messageId && this.sentMessages.delete(data._messageId)
145142
);
146143

147144
action({
@@ -155,14 +152,18 @@ export default new (class Live {
155152
}
156153

157154
send(event: string, payload: { _messageId?: string; [key: string]: any }) {
158-
const _messageId = identifier + messageIndex++;
155+
if (this.getConnectionsCount() < 2) {
156+
return Promise.resolve();
157+
}
158+
159+
const _messageId = this.identifier + this.messageIndex++;
159160
// eslint-disable-next-line
160161
payload._messageId = _messageId;
161-
sentMessages.set(_messageId, payload);
162+
this.sentMessages.set(_messageId, payload);
162163

163164
return new Promise((resolve, reject) => {
164-
if (channel) {
165-
channel
165+
if (this.channel) {
166+
this.channel
166167
.push(event, payload)
167168
.receive('ok', resolve)
168169
.receive('error', reject);
@@ -202,7 +203,7 @@ export default new (class Live {
202203
}
203204

204205
try {
205-
clients.get(moduleShortid).applyClient(operation);
206+
this.clients.get(moduleShortid).applyClient(operation);
206207
} catch (e) {
207208
// Something went wrong, probably a sync mismatch. Request new version
208209
this.send('live:module_state', {});
@@ -309,31 +310,59 @@ export default new (class Live {
309310
});
310311
}
311312

313+
getClient(moduleShortid: string) {
314+
return this.clients.get(moduleShortid);
315+
}
316+
312317
getAllClients() {
313-
return clients.getAll();
318+
return this.clients.getAll();
314319
}
315320

316321
applyClient(moduleShortid: string, operation: any) {
317-
return clients
322+
return this.clients
318323
.get(moduleShortid)
319324
.applyClient(TextOperation.fromJSON(operation));
320325
}
321326

322327
applyServer(moduleShortid: string, operation: any) {
323-
return clients
328+
return this.clients
324329
.get(moduleShortid)
325330
.applyServer(TextOperation.fromJSON(operation));
326331
}
327332

328333
serverAck(moduleShortid: string) {
329-
return clients.get(moduleShortid).serverAck();
334+
return this.clients.get(moduleShortid).serverAck();
330335
}
331336

332337
createClient(moduleShortid: string, revision: number) {
333-
return clients.create(moduleShortid, revision);
338+
return this.clients.create(moduleShortid, revision);
334339
}
335340

336341
resetClients() {
337-
clients.clear();
342+
this.clients.clear();
343+
}
344+
345+
private operationToElixir(ot) {
346+
return ot.map(op => {
347+
if (typeof op === 'number') {
348+
if (op < 0) {
349+
return { d: -op };
350+
}
351+
352+
return op;
353+
}
354+
355+
return { i: op };
356+
});
338357
}
339-
})();
358+
359+
private sendOperation(moduleShortid, revision, operation) {
360+
this.send('operation', {
361+
moduleShortid,
362+
operation: this.operationToElixir(operation.toJSON()),
363+
revision: Number(revision),
364+
});
365+
}
366+
}
367+
368+
export default new Live();
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// eslint-disable-next-line
2+
declare module 'ot' {
3+
export class Client {
4+
constructor(revision: number);
5+
revision: number;
6+
serverAck(): void;
7+
clientAck(): void;
8+
applyClient(operation: TextOperation): void;
9+
applyServer(operation: TextOperation): void;
10+
serverReconnect(): void;
11+
}
12+
export class TextOperation {
13+
static fromJSON(operation: object): TextOperation;
14+
compose(operation: TextOperation): TextOperation;
15+
}
16+
}

packages/app/src/app/overmind/namespaces/editor/actions.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
} from '@codesandbox/common/lib/types';
1010
import { getTextOperation } from '@codesandbox/common/lib/utils/diff';
1111
import { convertTypeToStatus } from '@codesandbox/common/lib/utils/notifications';
12+
import { hasPermission } from '@codesandbox/common/lib/utils/permission';
13+
import { NotificationStatus } from '@codesandbox/notifications';
1214
import { Action, AsyncAction } from 'app/overmind';
1315
import { withLoadApp, withOwnedSandbox } from 'app/overmind/factories';
1416
import {
@@ -19,11 +21,9 @@ import {
1921
import { clearCorrectionsFromAction } from 'app/utils/corrections';
2022
import { json } from 'overmind';
2123

22-
import { hasPermission } from '@codesandbox/common/lib/utils/permission';
23-
import { NotificationStatus } from '@codesandbox/notifications';
2424
import eventToTransform from '../../utils/event-to-transform';
25-
import * as internalActions from './internalActions';
2625
import { SERVER } from '../../utils/items';
26+
import * as internalActions from './internalActions';
2727

2828
export const internal = internalActions;
2929

@@ -188,7 +188,11 @@ export const codeSaved: AsyncAction<{
188188
moduleShortid: string;
189189
cbID: string | null;
190190
}> = withOwnedSandbox(
191-
async ({ actions }, { code, moduleShortid, cbID }) => {
191+
async ({ actions, effects }, { code, moduleShortid, cbID }) => {
192+
const pendingOperation = effects.live.getClient(moduleShortid).flush();
193+
194+
// eslint-disable-next-line
195+
console.log('OKAY, READY TO SAVE MODULE CODE OPERATION', pendingOperation);
192196
actions.editor.internal.saveCode({
193197
code,
194198
moduleShortid,

0 commit comments

Comments
 (0)