js-dos 8.1.0 → 8.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/i18n.ts +4 -0
- package/src/sockdrive/js/src/sockdrive/cache.ts +8 -85
- package/src/sockdrive/js/src/sockdrive/drive.ts +20 -10
- package/src/sockdrive/js/src/test/test.ts +6 -5
- package/src/store/ui.ts +5 -0
- package/src/ui.tsx +36 -0
- package/src/v8/config.ts +2 -0
- package/src/window/dos/dos-window.tsx +8 -1
- package/src/ws/ws-sockdrive.ts +38 -16
- package/src/ws/ws-transport-layer.ts +31 -18
package/package.json
CHANGED
package/src/i18n.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { State } from "./store";
|
|
|
4
4
|
|
|
5
5
|
const translations: {[lang: string]: {[key: string]: string} } = {
|
|
6
6
|
ru: {
|
|
7
|
+
ws_outdated: "Необходимо обновить DOS Браузер",
|
|
8
|
+
update: "Обновить",
|
|
7
9
|
preloading_sockdrive: "Загрузка FAT",
|
|
8
10
|
filter: "Фильтр",
|
|
9
11
|
add: "Добавить",
|
|
@@ -75,6 +77,8 @@ const translations: {[lang: string]: {[key: string]: string} } = {
|
|
|
75
77
|
fat_write: "FAT запись",
|
|
76
78
|
},
|
|
77
79
|
en: {
|
|
80
|
+
ws_outdated: "Must update DOS Browser App",
|
|
81
|
+
update: "Update",
|
|
78
82
|
preloading_sockdrive: "Preloading FAT",
|
|
79
83
|
filter: "Filter",
|
|
80
84
|
add: "Add",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LRUMap } from "./lru";
|
|
2
2
|
|
|
3
3
|
export interface Cache {
|
|
4
|
-
read(sector: number): Uint8Array | null;
|
|
4
|
+
read(sector: number, originReadMode: boolean): Uint8Array | null;
|
|
5
5
|
write(sector: number, buffer: Uint8Array): boolean;
|
|
6
6
|
create(origin: number, buffer: Uint8Array): void;
|
|
7
7
|
getOrigin(sector: number): number;
|
|
@@ -19,12 +19,16 @@ export class BlockCache implements Cache {
|
|
|
19
19
|
this.lru = new LRUMap(Math.floor(memoryLimit / (aheadRange * sectorSize)));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
public read(sector: number): Uint8Array | null {
|
|
22
|
+
public read(sector: number, originReadMode: boolean): Uint8Array | null {
|
|
23
23
|
const origin = this.getOrigin(sector);
|
|
24
24
|
const cached = this.lru.get(origin) as Uint8Array;
|
|
25
25
|
if (cached) {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
if (originReadMode) {
|
|
27
|
+
return cached;
|
|
28
|
+
} else {
|
|
29
|
+
const offset = sector - origin;
|
|
30
|
+
return cached.slice(offset * this.sectorSize, (offset + 1) * this.sectorSize);
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
return null;
|
|
30
34
|
}
|
|
@@ -50,84 +54,3 @@ export class BlockCache implements Cache {
|
|
|
50
54
|
return this.lru.size * this.aheadRange * this.sectorSize;
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
|
-
|
|
54
|
-
export class SimpleCache implements Cache {
|
|
55
|
-
aheadRange: number;
|
|
56
|
-
sectorSize: number;
|
|
57
|
-
lru: LRUMap;
|
|
58
|
-
|
|
59
|
-
constructor(sectorSize: number, aheadRange: number, memoryLimit: number) {
|
|
60
|
-
this.aheadRange = aheadRange;
|
|
61
|
-
this.sectorSize = sectorSize;
|
|
62
|
-
this.lru = new LRUMap(Math.floor(memoryLimit / sectorSize));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
public read(sector: number): Uint8Array | null {
|
|
66
|
-
const cached = this.lru.get(sector) as Uint8Array;
|
|
67
|
-
if (cached) {
|
|
68
|
-
return cached;
|
|
69
|
-
}
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
public write(sector: number, buffer: Uint8Array) {
|
|
74
|
-
this.lru.set(sector, buffer);
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
public create(sector: number, buffer: Uint8Array) {
|
|
79
|
-
for (let i = 0; i < this.aheadRange; ++i) {
|
|
80
|
-
this.lru.set(sector + i, buffer.slice(i * this.sectorSize, (i + 1) * this.sectorSize));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
public getOrigin(sector: number) {
|
|
85
|
-
return sector;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
public memUsed() {
|
|
89
|
-
return this.lru.size * this.sectorSize;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export class BlockAndWriteCache implements Cache {
|
|
94
|
-
aheadRange: number;
|
|
95
|
-
sectorSize: number;
|
|
96
|
-
blockCache: BlockCache;
|
|
97
|
-
writeCache: SimpleCache;
|
|
98
|
-
|
|
99
|
-
constructor(sectorSize: number, aheadRange: number, memoryLimit: number) {
|
|
100
|
-
this.aheadRange = aheadRange;
|
|
101
|
-
this.sectorSize = sectorSize;
|
|
102
|
-
this.blockCache = new BlockCache(sectorSize, aheadRange, memoryLimit);
|
|
103
|
-
this.writeCache = new SimpleCache(sectorSize, aheadRange, Math.floor(memoryLimit / 10));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
read(sector: number) {
|
|
107
|
-
return this.blockCache.read(sector) || this.writeCache.read(sector);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
write(sector: number, buffer: Uint8Array) {
|
|
111
|
-
if (!this.blockCache.write(sector, buffer)) {
|
|
112
|
-
this.writeCache.write(sector, buffer);
|
|
113
|
-
}
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
create(origin: number, buffer: Uint8Array): void {
|
|
118
|
-
this.blockCache.create(origin, buffer);
|
|
119
|
-
for (let i = 0; i < this.aheadRange; ++i) {
|
|
120
|
-
if (this.writeCache.read(origin + i) != null) {
|
|
121
|
-
this.writeCache.write(origin + i, buffer.slice(i * this.sectorSize, (i + 1) * this.sectorSize));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
getOrigin(sector: number): number {
|
|
127
|
-
return this.blockCache.getOrigin(sector);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
memUsed(): number {
|
|
131
|
-
return this.blockCache.memUsed() + this.writeCache.memUsed();
|
|
132
|
-
}
|
|
133
|
-
}
|
|
@@ -39,7 +39,8 @@ export class Drive {
|
|
|
39
39
|
cache: Cache | null = null;
|
|
40
40
|
cleanup = () => {/**/};
|
|
41
41
|
|
|
42
|
-
openFn = (read: boolean, write: boolean, size: number, preloadQueue: number[]
|
|
42
|
+
openFn = (read: boolean, write: boolean, size: number, preloadQueue: number[],
|
|
43
|
+
aheadRange: number) => {/**/};
|
|
43
44
|
preloadProgressFn = (restBytes: number) => {/**/};
|
|
44
45
|
errorFn = (e: Error) => {/**/};
|
|
45
46
|
|
|
@@ -48,13 +49,16 @@ export class Drive {
|
|
|
48
49
|
preloadSectors: boolean;
|
|
49
50
|
preloadQueue: number[] = [];
|
|
50
51
|
|
|
52
|
+
originReadMode: boolean;
|
|
53
|
+
|
|
51
54
|
public constructor(endpoint: string,
|
|
52
55
|
owner: string,
|
|
53
56
|
drive: string,
|
|
54
57
|
token: string,
|
|
55
58
|
stats: Stats,
|
|
56
59
|
module: EmModule,
|
|
57
|
-
preloadSectors = true
|
|
60
|
+
preloadSectors = true,
|
|
61
|
+
originReadMode = false) {
|
|
58
62
|
this.sectorSize = 512;
|
|
59
63
|
this.module = module;
|
|
60
64
|
this.endpoint = endpoint;
|
|
@@ -67,6 +71,7 @@ export class Drive {
|
|
|
67
71
|
this.stats = stats;
|
|
68
72
|
this.retries = 3;
|
|
69
73
|
this.preloadSectors = preloadSectors;
|
|
74
|
+
this.originReadMode = originReadMode;
|
|
70
75
|
|
|
71
76
|
this.reconnect();
|
|
72
77
|
}
|
|
@@ -75,7 +80,8 @@ export class Drive {
|
|
|
75
80
|
this.errorFn = errorFn;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
public onOpen(openFn: (read: boolean, write: boolean, imageSize: number,
|
|
83
|
+
public onOpen(openFn: (read: boolean, write: boolean, imageSize: number,
|
|
84
|
+
preloadQueue: number[], aheadRange: number) => void) {
|
|
79
85
|
this.openFn = openFn;
|
|
80
86
|
}
|
|
81
87
|
|
|
@@ -136,7 +142,8 @@ export class Drive {
|
|
|
136
142
|
socket.addEventListener("message", onMessage);
|
|
137
143
|
this.openFn(true, mode === "write",
|
|
138
144
|
(Number.parseInt(sizeStr) ?? 2 * 1024 * 1024) * 1024,
|
|
139
|
-
this.preloadQueue
|
|
145
|
+
this.preloadQueue,
|
|
146
|
+
aheadRange);
|
|
140
147
|
resolve(socket);
|
|
141
148
|
|
|
142
149
|
if (this.preloadQueue.length > 0) {
|
|
@@ -216,11 +223,9 @@ export class Drive {
|
|
|
216
223
|
}
|
|
217
224
|
|
|
218
225
|
public read(sector: number, buffer: Ptr, sync: boolean): Promise<number> | number {
|
|
219
|
-
const cached = this.cache!.read(sector);
|
|
226
|
+
const cached = this.cache!.read(sector, this.originReadMode);
|
|
220
227
|
if (cached !== null) {
|
|
221
|
-
|
|
222
|
-
this.stats.cacheHit++;
|
|
223
|
-
}
|
|
228
|
+
this.stats.cacheHit++;
|
|
224
229
|
this.module.HEAPU8.set(cached, buffer);
|
|
225
230
|
return sync ? 0 : Promise.resolve(0);
|
|
226
231
|
} else if (sync) {
|
|
@@ -361,10 +366,15 @@ export class Drive {
|
|
|
361
366
|
this.cache!.create(origin,
|
|
362
367
|
this.readAheadBuffer.slice(aheadOffset, aheadOffset + this.aheadSize));
|
|
363
368
|
if (i == 0 && buffer as number >= 0) {
|
|
369
|
+
if (this.originReadMode && sector !== origin) {
|
|
370
|
+
throw new Error("Sector must be one of origins in originReadMode");
|
|
371
|
+
}
|
|
364
372
|
const offset = sector - origin;
|
|
365
373
|
// aheadOffset is zero
|
|
366
|
-
|
|
367
|
-
|
|
374
|
+
const from = offset * this.sectorSize;
|
|
375
|
+
this.module.HEAPU8.set(this.readAheadBuffer
|
|
376
|
+
.slice(from, from + (this.originReadMode ? this.aheadRange : 1) * this.sectorSize),
|
|
377
|
+
buffer as number);
|
|
368
378
|
}
|
|
369
379
|
}
|
|
370
380
|
|
|
@@ -182,14 +182,17 @@ async function runTests() {
|
|
|
182
182
|
assert.equal(await drive.read(0, 0, false), 0);
|
|
183
183
|
assert.deepEqual(module.HEAPU8.slice(0, 10), new Uint8Array([51, 192, 142, 208, 188, 0, 124, 251, 80, 7]));
|
|
184
184
|
assert.equal(1, stats.cacheMiss, "cache miss");
|
|
185
|
+
assert.equal(0, stats.cacheHit, "cache hit");
|
|
185
186
|
|
|
186
187
|
assert.equal(await drive.read(8192, 0, false), 0);
|
|
187
188
|
assert.deepEqual(module.HEAPU8.slice(0, 10), new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
188
189
|
assert.equal(2, stats.cacheMiss, "cache miss");
|
|
190
|
+
assert.equal(0, stats.cacheHit, "cache hit");
|
|
189
191
|
|
|
190
192
|
assert.equal(await drive.read(8448, 0, false), 0);
|
|
191
193
|
assert.deepEqual(module.HEAPU8.slice(256, 256 + 10), new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
|
|
192
|
-
assert.equal(
|
|
194
|
+
assert.equal(2, stats.cacheMiss, "cache miss");
|
|
195
|
+
assert.equal(1, stats.cacheHit, "cache hit");
|
|
193
196
|
});
|
|
194
197
|
|
|
195
198
|
testDrive("read cache", async (drive, module, stats) => {
|
|
@@ -213,16 +216,14 @@ async function runTests() {
|
|
|
213
216
|
});
|
|
214
217
|
|
|
215
218
|
testDrive("preload sectors", async (drive, module, stats, preloadQueue) => {
|
|
216
|
-
assert.ok(preloadQueue.length >=
|
|
219
|
+
assert.ok(preloadQueue.length >= 2);
|
|
217
220
|
let _0 = false;
|
|
218
|
-
let _8448 = false;
|
|
219
221
|
let _8192 = false;
|
|
220
222
|
for (const next of preloadQueue) {
|
|
221
223
|
_0 = _0 || next === 0;
|
|
222
224
|
_8192 = _8192 || next === 8192;
|
|
223
|
-
_8448 = _8448 || next === 8448;
|
|
224
225
|
}
|
|
225
|
-
assert.ok(_0 && _8192
|
|
226
|
+
assert.ok(_0 && _8192);
|
|
226
227
|
|
|
227
228
|
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
|
|
228
229
|
|
package/src/store/ui.ts
CHANGED
|
@@ -27,6 +27,7 @@ const initialState: {
|
|
|
27
27
|
toastTimeoutId: number,
|
|
28
28
|
background: string | null,
|
|
29
29
|
readOnlyWarning: boolean,
|
|
30
|
+
updateWsWarning: boolean,
|
|
30
31
|
cloudSaves: boolean,
|
|
31
32
|
} = {
|
|
32
33
|
modal: "none",
|
|
@@ -42,6 +43,7 @@ const initialState: {
|
|
|
42
43
|
toastTimeoutId: 0,
|
|
43
44
|
background: null,
|
|
44
45
|
readOnlyWarning: false,
|
|
46
|
+
updateWsWarning: false,
|
|
45
47
|
cloudSaves: true,
|
|
46
48
|
};
|
|
47
49
|
|
|
@@ -136,6 +138,9 @@ export const uiSlice = createSlice({
|
|
|
136
138
|
readOnlyWarning: (state, a: { payload: boolean }) => {
|
|
137
139
|
state.readOnlyWarning = a.payload;
|
|
138
140
|
},
|
|
141
|
+
updateWsWarning: (state, a: { payload: boolean }) => {
|
|
142
|
+
state.updateWsWarning = a.payload;
|
|
143
|
+
},
|
|
139
144
|
cloudSaves: (state, a: { payload: boolean }) => {
|
|
140
145
|
state.cloudSaves = a.payload;
|
|
141
146
|
},
|
package/src/ui.tsx
CHANGED
|
@@ -49,6 +49,7 @@ export function Ui() {
|
|
|
49
49
|
<Login />
|
|
50
50
|
<Toast />
|
|
51
51
|
<ReadOnlyWarning />
|
|
52
|
+
<UpdateWsWarning />
|
|
52
53
|
</div>;
|
|
53
54
|
};
|
|
54
55
|
|
|
@@ -102,3 +103,38 @@ function ReadOnlyWarning() {
|
|
|
102
103
|
</div>
|
|
103
104
|
</div>;
|
|
104
105
|
}
|
|
106
|
+
|
|
107
|
+
function UpdateWsWarning() {
|
|
108
|
+
const updateWsWarning = useSelector((state: State) => state.ui.updateWsWarning);
|
|
109
|
+
const t = useT();
|
|
110
|
+
const dispatch = useDispatch();
|
|
111
|
+
|
|
112
|
+
if (!updateWsWarning) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function fix() {
|
|
117
|
+
window.open("https://dos.zone/download/", "_blank");
|
|
118
|
+
dispatch(uiSlice.actions.updateWsWarning(false));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function close() {
|
|
122
|
+
dispatch(uiSlice.actions.updateWsWarning(false));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return <div class="absolute left-20 right-10 top-10 flex justify-center">
|
|
126
|
+
<div class="alert w-auto">
|
|
127
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
|
128
|
+
stroke-width="1.5" stroke="currentColor" class="size-6 text-error">
|
|
129
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217
|
|
130
|
+
3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898
|
|
131
|
+
0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
|
|
132
|
+
</svg>
|
|
133
|
+
<span>{t("ws_outdated")}</span>
|
|
134
|
+
<div>
|
|
135
|
+
<button class="btn btn-sm btn-primary mr-2" onClick={fix}>{t("update")}</button>
|
|
136
|
+
<button class="btn btn-sm" onClick={close}>{t("close")}</button>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>;
|
|
140
|
+
}
|
package/src/v8/config.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { State, useNonSerializableStore, postJsDosEvent } from "../../store";
|
|
|
6
6
|
import { useDosRuntime } from "./dos-runtime";
|
|
7
7
|
import { dhry2Bundle, Dhry2Results } from "./dos-dhry2";
|
|
8
8
|
import { createWsTransportLayer } from "../../ws/ws-transport-layer";
|
|
9
|
+
import { actualWsVersion } from "../../v8/config";
|
|
10
|
+
import { uiSlice } from "../../store/ui";
|
|
9
11
|
|
|
10
12
|
declare const emulators: Emulators;
|
|
11
13
|
|
|
@@ -35,7 +37,12 @@ export function DosWindow(props: {
|
|
|
35
37
|
if (backendHardware && nonSerializableStore.options.backendHardware) {
|
|
36
38
|
const ws = await nonSerializableStore.options.backendHardware(backend);
|
|
37
39
|
if (ws !== null) {
|
|
38
|
-
return emulators.backend(bundles as any, await createWsTransportLayer(ws
|
|
40
|
+
return emulators.backend(bundles as any, await createWsTransportLayer(ws, (version) => {
|
|
41
|
+
if (version < actualWsVersion) {
|
|
42
|
+
dispatch(uiSlice.actions.updateWsWarning(true));
|
|
43
|
+
}
|
|
44
|
+
console.log("wsServer:", version, " expected:", actualWsVersion);
|
|
45
|
+
}), { token });
|
|
39
46
|
}
|
|
40
47
|
}
|
|
41
48
|
|
package/src/ws/ws-sockdrive.ts
CHANGED
|
@@ -16,8 +16,10 @@ export interface ReadResponse {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function createSockdrive(
|
|
19
|
-
onOpen: (drive: string, read: boolean, write: boolean) => void,
|
|
20
|
-
onError: (e: Error) => void
|
|
19
|
+
onOpen: (drive: string, read: boolean, write: boolean, imageSize: number, preloadQueue: number[]) => void,
|
|
20
|
+
onError: (e: Error) => void,
|
|
21
|
+
onPreloadProgress: (drive: string, restBytes: number) => void,
|
|
22
|
+
) {
|
|
21
23
|
let seq = 0;
|
|
22
24
|
const mapping: { [handle: Handle]: Drive } = {};
|
|
23
25
|
const templates: { [handle: number]: Template } = {};
|
|
@@ -32,7 +34,10 @@ export function createSockdrive(
|
|
|
32
34
|
};
|
|
33
35
|
const sockdrive = {
|
|
34
36
|
stats,
|
|
35
|
-
open: async (url: string, owner: string, name: string, token: string
|
|
37
|
+
open: async (url: string, owner: string, name: string, token: string): Promise<{
|
|
38
|
+
handle: Handle,
|
|
39
|
+
aheadRange: number,
|
|
40
|
+
}> => {
|
|
36
41
|
const response = await fetch(url.replace("wss://", "https://")
|
|
37
42
|
.replace("ws://", "http://") + "/template/" + owner + "/" + name);
|
|
38
43
|
const template = await response.json();
|
|
@@ -50,22 +55,39 @@ export function createSockdrive(
|
|
|
50
55
|
};
|
|
51
56
|
|
|
52
57
|
const sectorSize = templates[seq].sectorSize;
|
|
53
|
-
|
|
54
|
-
mapping[seq] = new Drive(url, owner, name, token, stats,
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
const module = { HEAPU8: new Uint8Array(0) };
|
|
59
|
+
mapping[seq] = new Drive(url, owner, name, token, stats, module, true, true);
|
|
60
|
+
return new Promise<{ handle: Handle, aheadRange: number }>((resolve, reject) => {
|
|
61
|
+
const drive = owner + "/" + name;
|
|
62
|
+
mapping[seq].onOpen((read, write, imageSize, preloadQueue, aheadRange) => {
|
|
63
|
+
memory[seq] = new Uint8Array(sectorSize /* write */ + sectorSize * aheadRange);
|
|
64
|
+
module.HEAPU8 = memory[seq];
|
|
65
|
+
onOpen(drive, read, write, imageSize, preloadQueue);
|
|
66
|
+
resolve({
|
|
67
|
+
handle: seq,
|
|
68
|
+
aheadRange,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
mapping[seq].onPreloadProgress((restBytes) => {
|
|
72
|
+
onPreloadProgress(drive, restBytes);
|
|
73
|
+
});
|
|
74
|
+
mapping[seq].onError((e) => {
|
|
75
|
+
onError(e);
|
|
76
|
+
reject(e);
|
|
77
|
+
});
|
|
57
78
|
});
|
|
58
|
-
mapping[seq].onError(onError);
|
|
59
|
-
return seq;
|
|
60
79
|
},
|
|
61
|
-
read: (handle: Handle, sector: number): Promise<ReadResponse> => {
|
|
80
|
+
read: async (handle: Handle, sector: number): Promise<ReadResponse> => {
|
|
62
81
|
if (mapping[handle]) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
const ptr = templates[seq].sectorSize;
|
|
83
|
+
let code = mapping[handle].read(sector, ptr, true) as number;
|
|
84
|
+
if (code = 255) {
|
|
85
|
+
code = await mapping[handle].read(sector, ptr, false);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
code,
|
|
89
|
+
buffer: memory[handle].slice(ptr),
|
|
90
|
+
};
|
|
69
91
|
}
|
|
70
92
|
|
|
71
93
|
console.error("ERROR! sockdrive handle", handle, "not found");
|
|
@@ -48,9 +48,11 @@ export class WsTransportLayer implements TransportLayer {
|
|
|
48
48
|
socket: WebSocket;
|
|
49
49
|
sessionId: string = Date.now() + "";
|
|
50
50
|
hardware: Hardware;
|
|
51
|
+
onInit: (version: number) => void = {/**/};
|
|
51
52
|
|
|
52
53
|
private cycles = 0;
|
|
53
|
-
private sockdrive = createSockdrive(this.onSockdriveOpen.bind(this),
|
|
54
|
+
private sockdrive = createSockdrive(this.onSockdriveOpen.bind(this),
|
|
55
|
+
this.onSockdriveError.bind(this), this.onSockdrivePreloadProgress.bind(this));
|
|
54
56
|
|
|
55
57
|
private handler: MessageHandler = () => {/**/};
|
|
56
58
|
|
|
@@ -120,15 +122,17 @@ export class WsTransportLayer implements TransportLayer {
|
|
|
120
122
|
const { id, payload } = this.readMessage(msg);
|
|
121
123
|
const message = serverMessageEnum[id];
|
|
122
124
|
switch (message) {
|
|
123
|
-
case "ws-ready":
|
|
125
|
+
case "ws-ready": {
|
|
126
|
+
this.onInit((payload && payload[0] && payload.length > 0) ? payload[0][0] : 0);
|
|
127
|
+
this.handler(message, {});
|
|
128
|
+
} break;
|
|
124
129
|
case "ws-server-ready":
|
|
125
|
-
case "ws-exit":
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
} break;
|
|
130
|
+
case "ws-exit": {
|
|
131
|
+
// delay ws-server-ready until ws-sound-init
|
|
132
|
+
if (message !== "ws-server-ready") {
|
|
133
|
+
this.handler(message, {});
|
|
134
|
+
}
|
|
135
|
+
} break;
|
|
132
136
|
case "ws-stdout":
|
|
133
137
|
case "ws-log":
|
|
134
138
|
case "ws-warn":
|
|
@@ -267,17 +271,17 @@ export class WsTransportLayer implements TransportLayer {
|
|
|
267
271
|
const owner = textDecoder.decode(payload[1]!);
|
|
268
272
|
const name = textDecoder.decode(payload[2]!);
|
|
269
273
|
const token = textDecoder.decode(payload[3]!);
|
|
270
|
-
const aheadRange = this.
|
|
271
|
-
const handle = await this.sockdrive.open(url, owner, name, token, aheadRange);
|
|
274
|
+
const { handle, aheadRange } = await this.sockdrive.open(url, owner, name, token);
|
|
272
275
|
const template = this.sockdrive.template(handle);
|
|
273
|
-
const packet = new Uint8Array(4 *
|
|
276
|
+
const packet = new Uint8Array(4 * 7);
|
|
274
277
|
let offset = 0;
|
|
275
278
|
offset = this.writeUint32(packet, handle, offset);
|
|
276
279
|
offset = this.writeUint32(packet, template.size, offset);
|
|
277
280
|
offset = this.writeUint32(packet, template.heads, offset);
|
|
278
281
|
offset = this.writeUint32(packet, template.cylinders, offset);
|
|
279
282
|
offset = this.writeUint32(packet, template.sectors, offset);
|
|
280
|
-
this.writeUint32(packet, template.sectorSize, offset);
|
|
283
|
+
offset = this.writeUint32(packet, template.sectorSize, offset);
|
|
284
|
+
this.writeUint32(packet, aheadRange, offset);
|
|
281
285
|
this.sendMessageToSocket(100, packet);
|
|
282
286
|
} break;
|
|
283
287
|
case 101/* ws-sockdrive-read */: {
|
|
@@ -317,7 +321,7 @@ export class WsTransportLayer implements TransportLayer {
|
|
|
317
321
|
}
|
|
318
322
|
}
|
|
319
323
|
|
|
320
|
-
constructor(socket: WebSocket) {
|
|
324
|
+
constructor(socket: WebSocket, onInit: (version: number) => void) {
|
|
321
325
|
this.socket = socket;
|
|
322
326
|
this.socket.addEventListener("error", (e) => {
|
|
323
327
|
this.handler("ws-err", { tag: "ws", message: (e as any).message ?? "Unknown transport layer error" });
|
|
@@ -330,6 +334,7 @@ export class WsTransportLayer implements TransportLayer {
|
|
|
330
334
|
});
|
|
331
335
|
this.sendMessageToSocket("wc-install");
|
|
332
336
|
this.hardware = (null) as any;
|
|
337
|
+
this.onInit = onInit;
|
|
333
338
|
}
|
|
334
339
|
|
|
335
340
|
async sendMessageToServer(name: ClientMessage, props?: { [key: string]: any; }) {
|
|
@@ -411,10 +416,11 @@ export class WsTransportLayer implements TransportLayer {
|
|
|
411
416
|
this.sendMessageToSocket("wc-exit");
|
|
412
417
|
}
|
|
413
418
|
|
|
414
|
-
onSockdriveOpen(drive: string, read: boolean, write: boolean) {
|
|
419
|
+
onSockdriveOpen(drive: string, read: boolean, write: boolean, imageSize: number, preloadQueue: number[]) {
|
|
415
420
|
this.handler("ws-log", {
|
|
416
421
|
tag: "worker",
|
|
417
|
-
message: "sockdrive: " + drive + ", read=" + read + ", write=" + write
|
|
422
|
+
message: "sockdrive: " + drive + ", read=" + read + ", write=" + write +
|
|
423
|
+
", imageSize=" + Math.round(imageSize / 1024 / 1024) + "Mb" + ", preloadQueue=" + preloadQueue.length,
|
|
418
424
|
});
|
|
419
425
|
}
|
|
420
426
|
|
|
@@ -424,9 +430,16 @@ export class WsTransportLayer implements TransportLayer {
|
|
|
424
430
|
message: e.message ?? "unable to open sockdrive",
|
|
425
431
|
});
|
|
426
432
|
}
|
|
433
|
+
|
|
434
|
+
onSockdrivePreloadProgress(drive: string, restBytes: number) {
|
|
435
|
+
this.handler("ws-log", {
|
|
436
|
+
tag: "worker",
|
|
437
|
+
message: "sockdrive: " + drive + ", preload=" + restBytes,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
427
440
|
}
|
|
428
441
|
|
|
429
|
-
export function createWsTransportLayer(url: string): Promise<TransportLayer> {
|
|
442
|
+
export function createWsTransportLayer(url: string, onInit: (version: number) => void): Promise<TransportLayer> {
|
|
430
443
|
return new Promise<TransportLayer>((resolve) => {
|
|
431
444
|
let locked = false;
|
|
432
445
|
const inervalId = setInterval(() => {
|
|
@@ -439,7 +452,7 @@ export function createWsTransportLayer(url: string): Promise<TransportLayer> {
|
|
|
439
452
|
const onSuccess = () => {
|
|
440
453
|
clearInterval(inervalId);
|
|
441
454
|
console.log("Connected to", url);
|
|
442
|
-
resolve(new WsTransportLayer(ws));
|
|
455
|
+
resolve(new WsTransportLayer(ws, onInit));
|
|
443
456
|
};
|
|
444
457
|
|
|
445
458
|
ws.addEventListener("error", (error) => {
|