@@ -35,6 +35,16 @@ export const setGlobalStore = (store) => {
35
35
export const backendUpdate = createAction ( 'backend/update' ) ;
36
36
export const backendSetSharedState = createAction ( 'backend/setSharedState' ) ;
37
37
export const backendSuspendStart = createAction ( 'backend/suspendStart' ) ;
38
+ export const backendCreatePayloadQueue = createAction (
39
+ 'backend/createPayloadQueue' ,
40
+ ) ;
41
+ export const backendDequeuePayloadQueue = createAction (
42
+ 'backend/dequeuePayloadQueue' ,
43
+ ) ;
44
+ export const backendRemovePayloadQueue = createAction (
45
+ 'backend/removePayloadQueue' ,
46
+ ) ;
47
+ export const nextPayloadChunk = createAction ( 'nextPayloadChunk' ) ;
38
48
39
49
export const backendSuspendSuccess = ( ) => ( {
40
50
type : 'backend/suspendSuccess' ,
@@ -47,6 +57,7 @@ const initialState = {
47
57
config : { } ,
48
58
data : { } ,
49
59
shared : { } ,
60
+ outgoingPayloadQueues : { } as Record < string , string [ ] > ,
50
61
// Start as suspended
51
62
suspended : Date . now ( ) ,
52
63
suspending : false ,
@@ -123,6 +134,44 @@ export const backendReducer = (state = initialState, action) => {
123
134
} ;
124
135
}
125
136
137
+ if ( type === 'backend/createPayloadQueue' ) {
138
+ const { id, chunks } = payload ;
139
+ const { outgoingPayloadQueues } = state ;
140
+ return {
141
+ ...state ,
142
+ outgoingPayloadQueues : {
143
+ ...outgoingPayloadQueues ,
144
+ [ id ] : chunks ,
145
+ } ,
146
+ } ;
147
+ }
148
+
149
+ if ( type === 'backend/dequeuePayloadQueue' ) {
150
+ const { id } = payload ;
151
+ const { outgoingPayloadQueues } = state ;
152
+ const { [ id ] : targetQueue , ...otherQueues } = outgoingPayloadQueues ;
153
+ const [ _ , ...rest ] = targetQueue ;
154
+ return {
155
+ ...state ,
156
+ outgoingPayloadQueues : rest . length
157
+ ? {
158
+ ...otherQueues ,
159
+ [ id ] : rest ,
160
+ }
161
+ : otherQueues ,
162
+ } ;
163
+ }
164
+
165
+ if ( type === 'backend/removePayloadQueue' ) {
166
+ const { id } = payload ;
167
+ const { outgoingPayloadQueues } = state ;
168
+ const { [ id ] : _ , ...otherQueues } = outgoingPayloadQueues ;
169
+ return {
170
+ ...state ,
171
+ outgoingPayloadQueues : otherQueues ,
172
+ } ;
173
+ }
174
+
126
175
return state ;
127
176
} ;
128
177
@@ -131,7 +180,9 @@ export const backendMiddleware = (store) => {
131
180
let suspendInterval ;
132
181
133
182
return ( next ) => ( action ) => {
134
- const { suspended } = selectBackend ( store . getState ( ) ) ;
183
+ const { suspended, outgoingPayloadQueues } = selectBackend (
184
+ store . getState ( ) ,
185
+ ) ;
135
186
const { type, payload } = action ;
136
187
137
188
if ( type === 'update' ) {
@@ -234,10 +285,87 @@ export const backendMiddleware = (store) => {
234
285
} ) ;
235
286
}
236
287
288
+ if ( type === 'oversizePayloadResponse' ) {
289
+ const { allow } = payload ;
290
+ if ( allow ) {
291
+ store . dispatch ( nextPayloadChunk ( payload ) ) ;
292
+ } else {
293
+ store . dispatch ( backendRemovePayloadQueue ( payload ) ) ;
294
+ }
295
+ }
296
+
297
+ if ( type === 'acknowlegePayloadChunk' ) {
298
+ store . dispatch ( backendDequeuePayloadQueue ( payload ) ) ;
299
+ store . dispatch ( nextPayloadChunk ( payload ) ) ;
300
+ }
301
+
302
+ if ( type === 'nextPayloadChunk' ) {
303
+ const { id } = payload ;
304
+ const chunk = outgoingPayloadQueues [ id ] [ 0 ] ;
305
+ Byond . sendMessage ( 'payloadChunk' , {
306
+ id,
307
+ chunk,
308
+ } ) ;
309
+ }
310
+
237
311
return next ( action ) ;
238
312
} ;
239
313
} ;
240
314
315
+ const encodedLengthBinarySearch = ( haystack : string [ ] , length : number ) => {
316
+ const haystackLength = haystack . length ;
317
+ let high = haystackLength - 1 ;
318
+ let low = 0 ;
319
+ let mid = 0 ;
320
+ while ( low < high ) {
321
+ mid = Math . round ( ( low + high ) / 2 ) ;
322
+ const substringLength = encodeURIComponent (
323
+ haystack . slice ( 0 , mid ) . join ( '' ) ,
324
+ ) . length ;
325
+ if ( substringLength === length ) {
326
+ break ;
327
+ }
328
+ if ( substringLength < length ) {
329
+ low = mid + 1 ;
330
+ } else {
331
+ high = mid - 1 ;
332
+ }
333
+ }
334
+ return mid ;
335
+ } ;
336
+
337
+ const chunkSplitter = {
338
+ [ Symbol . split ] : ( string : string ) => {
339
+ // TODO: get rid of the "as any" whenever we upgrade typescript
340
+ const charSeq = ( string [ Symbol . iterator ] ( ) as any ) . toArray ( ) ;
341
+ const length = charSeq . length ;
342
+ let chunks : string [ ] = [ ] ;
343
+ let startIndex = 0 ;
344
+ let endIndex = 1024 ;
345
+ while ( startIndex < length ) {
346
+ const cut = charSeq . slice (
347
+ startIndex ,
348
+ endIndex < length ? endIndex : undefined ,
349
+ ) ;
350
+ const cutString = cut . join ( '' ) ;
351
+ if ( encodeURIComponent ( cutString ) . length > 1024 ) {
352
+ const splitIndex = startIndex + encodedLengthBinarySearch ( cut , 1024 ) ;
353
+ chunks . push (
354
+ charSeq
355
+ . slice ( startIndex , splitIndex < length ? splitIndex : undefined )
356
+ . join ( '' ) ,
357
+ ) ;
358
+ startIndex = splitIndex ;
359
+ } else {
360
+ chunks . push ( cutString ) ;
361
+ startIndex = endIndex ;
362
+ }
363
+ endIndex = startIndex + 1024 ;
364
+ }
365
+ return chunks ;
366
+ } ,
367
+ } ;
368
+
241
369
/**
242
370
* Sends an action to `ui_act` on `src_object` that this tgui window
243
371
* is associated with.
@@ -252,6 +380,31 @@ export const sendAct = (action: string, payload: object = {}) => {
252
380
logger . error ( `Payload for act() must be an object, got this:` , payload ) ;
253
381
return ;
254
382
}
383
+ if ( ! Byond . TRIDENT ) {
384
+ const stringifiedPayload = JSON . stringify ( payload ) ;
385
+ const urlSize = Object . entries ( {
386
+ type : 'act/' + action ,
387
+ payload : stringifiedPayload ,
388
+ tgui : 1 ,
389
+ windowId : Byond . windowId ,
390
+ } ) . reduce (
391
+ ( url , [ key , value ] , i ) =>
392
+ url +
393
+ `${ i > 0 ? '&' : '?' } ${ encodeURIComponent ( key ) } =${ encodeURIComponent ( value ) } ` ,
394
+ '' ,
395
+ ) . length ;
396
+ if ( urlSize > 2048 ) {
397
+ let chunks : string [ ] = stringifiedPayload . split ( chunkSplitter ) ;
398
+ const id = `${ Date . now ( ) } ` ;
399
+ globalStore ?. dispatch ( backendCreatePayloadQueue ( { id, chunks } ) ) ;
400
+ Byond . sendMessage ( 'oversizedPayloadRequest' , {
401
+ type : 'act/' + action ,
402
+ id,
403
+ chunkCount : chunks . length ,
404
+ } ) ;
405
+ return ;
406
+ }
407
+ }
255
408
Byond . sendMessage ( 'act/' + action , payload ) ;
256
409
} ;
257
410
@@ -279,6 +432,7 @@ type BackendState<TData> = {
279
432
} ;
280
433
data : TData ;
281
434
shared : Record < string , any > ;
435
+ outgoingPayloadQueues : Record < string , any [ ] > ;
282
436
suspending : boolean ;
283
437
suspended : boolean ;
284
438
debug ?: {
0 commit comments