js-dos 0.80.25

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.
Files changed (119) hide show
  1. package/.eslintrc.json +43 -0
  2. package/.github/workflows/build.yml +43 -0
  3. package/.gitmodules +4 -0
  4. package/README.md +24 -0
  5. package/index.html +106 -0
  6. package/package.json +48 -0
  7. package/postcss.config.cjs +8 -0
  8. package/src/app.css +14 -0
  9. package/src/base.css +10 -0
  10. package/src/components/checkbox.tsx +41 -0
  11. package/src/components/close-button.tsx +17 -0
  12. package/src/components/components.css +28 -0
  13. package/src/components/dos-option-checkbox.tsx +72 -0
  14. package/src/components/dos-option-select.tsx +94 -0
  15. package/src/components/dos-option-slider.tsx +35 -0
  16. package/src/components/error.tsx +16 -0
  17. package/src/components/loading.tsx +26 -0
  18. package/src/components/lock.tsx +10 -0
  19. package/src/components/select.tsx +33 -0
  20. package/src/components/slider.tsx +69 -0
  21. package/src/download-file.ts +18 -0
  22. package/src/frame/account-frame.tsx +155 -0
  23. package/src/frame/editor/defaults.ts +344 -0
  24. package/src/frame/editor/editor-conf-frame.tsx +42 -0
  25. package/src/frame/editor/editor-frame.css +55 -0
  26. package/src/frame/editor/editor-fs-frame.tsx +315 -0
  27. package/src/frame/frame.css +127 -0
  28. package/src/frame/frame.tsx +34 -0
  29. package/src/frame/host-cache-frame.tsx +124 -0
  30. package/src/frame/image-rendering-frame.tsx +8 -0
  31. package/src/frame/network-frame.tsx +157 -0
  32. package/src/frame/quick-save-frame.tsx +95 -0
  33. package/src/frame/settings-frame.tsx +14 -0
  34. package/src/frame/stats-frame.tsx +82 -0
  35. package/src/host/bundle-storage.ts +91 -0
  36. package/src/host/fullscreen.ts +36 -0
  37. package/src/host/lcache.ts +203 -0
  38. package/src/host/lstorage.ts +156 -0
  39. package/src/i18n.ts +167 -0
  40. package/src/index.css +7 -0
  41. package/src/load.ts +139 -0
  42. package/src/login/login.css +9 -0
  43. package/src/login/login.tsx +77 -0
  44. package/src/main.tsx +195 -0
  45. package/src/non-serializable-store.ts +35 -0
  46. package/src/public/types.ts +39 -0
  47. package/src/sidebar/account-button.tsx +32 -0
  48. package/src/sidebar/cloud-save-button.tsx +89 -0
  49. package/src/sidebar/diskette-icon.tsx +14 -0
  50. package/src/sidebar/fullscreen-button.tsx +44 -0
  51. package/src/sidebar/network-button.tsx +39 -0
  52. package/src/sidebar/sidebar-button.tsx +243 -0
  53. package/src/sidebar/sidebar.css +47 -0
  54. package/src/sidebar/sidebar.tsx +30 -0
  55. package/src/sockdrive/LICENSE +674 -0
  56. package/src/sockdrive/README.md +22 -0
  57. package/src/sockdrive/SDL2/LRUCache11.hpp +240 -0
  58. package/src/sockdrive/SDL2/sockdrive.cpp +207 -0
  59. package/src/sockdrive/include/sockdrive.h +23 -0
  60. package/src/sockdrive/js/.eslintrc.json +43 -0
  61. package/src/sockdrive/js/README.md +70 -0
  62. package/src/sockdrive/js/package.json +29 -0
  63. package/src/sockdrive/js/src/fatfs/chains.js +318 -0
  64. package/src/sockdrive/js/src/fatfs/dir.js +385 -0
  65. package/src/sockdrive/js/src/fatfs/helpers.js +210 -0
  66. package/src/sockdrive/js/src/fatfs/index.js +606 -0
  67. package/src/sockdrive/js/src/fatfs/structs.js +255 -0
  68. package/src/sockdrive/js/src/fatfs/vol.js +166 -0
  69. package/src/sockdrive/js/src/sockdrive/cache.ts +133 -0
  70. package/src/sockdrive/js/src/sockdrive/drive.ts +364 -0
  71. package/src/sockdrive/js/src/sockdrive/lru.ts +221 -0
  72. package/src/sockdrive/js/src/sockdrive/types.ts +10 -0
  73. package/src/sockdrive/js/src/sockdrive-fat.d.ts +64 -0
  74. package/src/sockdrive/js/src/sockdrive-fat.ts +228 -0
  75. package/src/sockdrive/js/src/sockdrive-native.ts +98 -0
  76. package/src/sockdrive/js/src/test/test.ts +186 -0
  77. package/src/sockdrive/js/tsconfig.json +11 -0
  78. package/src/sockdrive/js/webpack.config.js +68 -0
  79. package/src/sockdrive/js/yarn.lock +1808 -0
  80. package/src/sockdrive/lz4/lz4.c +2773 -0
  81. package/src/sockdrive/lz4/lz4.h +862 -0
  82. package/src/store/auth.ts +104 -0
  83. package/src/store/dos.ts +284 -0
  84. package/src/store/editor.ts +59 -0
  85. package/src/store/storage.ts +30 -0
  86. package/src/store/ui.ts +183 -0
  87. package/src/store.ts +27 -0
  88. package/src/ui.tsx +103 -0
  89. package/src/v8/changes.ts +56 -0
  90. package/src/v8/config.ts +18 -0
  91. package/src/v8/subscriptions.ts +62 -0
  92. package/src/vite-env.d.ts +2 -0
  93. package/src/window/dos/controls/keyboard.ts +32 -0
  94. package/src/window/dos/controls/keys.ts +331 -0
  95. package/src/window/dos/controls/mouse/mount.ts +99 -0
  96. package/src/window/dos/controls/mouse/mouse-locked.ts +51 -0
  97. package/src/window/dos/controls/mouse/mouse-not-locked.ts +84 -0
  98. package/src/window/dos/controls/mouse/mouse-swipe.ts +57 -0
  99. package/src/window/dos/controls/mouse/pointer.ts +85 -0
  100. package/src/window/dos/controls/mouse.ts +21 -0
  101. package/src/window/dos/dos-dhry2.tsx +191 -0
  102. package/src/window/dos/dos-runtime.tsx +185 -0
  103. package/src/window/dos/dos-window.tsx +82 -0
  104. package/src/window/dos/render/canvas.ts +65 -0
  105. package/src/window/dos/render/resize.ts +31 -0
  106. package/src/window/dos/render/webgl.ts +173 -0
  107. package/src/window/dos/sound/audio-node.ts +148 -0
  108. package/src/window/error-window.tsx +9 -0
  109. package/src/window/loading-window.tsx +54 -0
  110. package/src/window/prerun-window.tsx +98 -0
  111. package/src/window/select-window.tsx +118 -0
  112. package/src/window/window.css +78 -0
  113. package/src/window/window.tsx +49 -0
  114. package/src/ws/ws-sockdrive.ts +102 -0
  115. package/src/ws/ws-transport-layer.ts +457 -0
  116. package/tailwind.config.cjs +48 -0
  117. package/tsconfig.json +32 -0
  118. package/tsconfig.node.json +9 -0
  119. package/vite.config.ts +22 -0
@@ -0,0 +1,51 @@
1
+ import { CommandInterface } from "emulators";
2
+ import { mount } from "./mount";
3
+
4
+ export function mouseCapture(sensitivity: number,
5
+ pointerButton: number,
6
+ el: HTMLElement,
7
+ ci: CommandInterface) {
8
+ function isNotLocked() {
9
+ return document.pointerLockElement !== el;
10
+ }
11
+
12
+ function onMouseDown(x: number, y: number, button: number) {
13
+ if (isNotLocked()) {
14
+ const requestPointerLock = el.requestPointerLock ||
15
+ (el as any).mozRequestPointerLock ||
16
+ (el as any).webkitRequestPointerLock;
17
+
18
+ requestPointerLock.call(el);
19
+
20
+ return;
21
+ }
22
+
23
+ ci.sendMouseButton(button, true);
24
+ }
25
+
26
+ function onMouseUp(x: number, y: number, button: number) {
27
+ if (isNotLocked()) {
28
+ return;
29
+ }
30
+
31
+ ci.sendMouseButton(button, false);
32
+ }
33
+
34
+ function onMouseMove(x: number, y: number, mX: number, mY: number) {
35
+ if (isNotLocked()) {
36
+ return;
37
+ }
38
+
39
+ if (mX === 0 && mY === 0) {
40
+ return;
41
+ }
42
+
43
+ (ci as any).sendMouseRelativeMotion(mX * sensitivity, mY * sensitivity);
44
+ }
45
+
46
+ function onMouseLeave(x: number, y: number) {
47
+ // nothing to do
48
+ }
49
+
50
+ return mount(el, pointerButton, onMouseDown, onMouseMove, onMouseUp, onMouseLeave);
51
+ }
@@ -0,0 +1,84 @@
1
+ import { CommandInterface } from "emulators";
2
+ import { mount } from "./mount";
3
+
4
+ const insensitivePadding = 1 / 100;
5
+
6
+ export function mouseDefault(pointerNumber: number,
7
+ el: HTMLElement,
8
+ ci: CommandInterface) {
9
+ const mapXY = (x: number, y: number) => doMapXY(x, y, el, ci);
10
+
11
+ if (document.pointerLockElement === el) {
12
+ document.exitPointerLock();
13
+ }
14
+
15
+ function onMouseDown(x: number, y: number, button: number) {
16
+ const xy = mapXY(x, y);
17
+ ci.sendMouseMotion(xy.x, xy.y);
18
+ ci.sendMouseButton(button, true);
19
+ }
20
+
21
+ function onMouseUp(x: number, y: number, button: number) {
22
+ const xy = mapXY(x, y);
23
+ ci.sendMouseMotion(xy.x, xy.y);
24
+ ci.sendMouseButton(button, false);
25
+ }
26
+
27
+ function onMouseMove(x: number, y: number, mX: number, mY: number) {
28
+ const xy = mapXY(x, y);
29
+ ci.sendMouseMotion(xy.x, xy.y);
30
+ }
31
+
32
+ function onMouseLeave(x: number, y: number) {
33
+ const xy = mapXY(x, y);
34
+ ci.sendMouseMotion(xy.x, xy.y);
35
+ }
36
+
37
+ return mount(el, pointerNumber, onMouseDown, onMouseMove, onMouseUp, onMouseLeave);
38
+ }
39
+
40
+ function doMapXY(eX: number,
41
+ eY: number,
42
+ el: HTMLElement,
43
+ ci: CommandInterface) {
44
+ const { width: containerWidth, height: containerHeight } = el.getBoundingClientRect();
45
+ const frameWidth = ci.width();
46
+ const frameHeight = ci.height();
47
+
48
+ const aspect = frameWidth / frameHeight;
49
+
50
+ let width = containerWidth;
51
+ let height = containerWidth / aspect;
52
+
53
+ if (height > containerHeight) {
54
+ height = containerHeight;
55
+ width = containerHeight * aspect;
56
+ }
57
+
58
+ const top = (containerHeight - height) / 2;
59
+ const left = (containerWidth - width) / 2;
60
+
61
+ let x = Math.max(0, Math.min(1, (eX - left) / width));
62
+ let y = Math.max(0, Math.min(1, (eY - top) / height));
63
+
64
+ if (x <= insensitivePadding) {
65
+ x = 0;
66
+ }
67
+
68
+ if (x >= (1 - insensitivePadding)) {
69
+ x = 1;
70
+ }
71
+
72
+ if (y <= insensitivePadding) {
73
+ y = 0;
74
+ }
75
+
76
+ if (y >= (1 - insensitivePadding)) {
77
+ y = 1;
78
+ }
79
+
80
+ return {
81
+ x,
82
+ y,
83
+ };
84
+ }
@@ -0,0 +1,57 @@
1
+ import { CommandInterface } from "emulators";
2
+ import { mount } from "./mount";
3
+
4
+ const clickDelay = 500;
5
+ const clickThreshold = 50;
6
+
7
+ export function mouseSwipe(sensitivity: number,
8
+ pointerButton: number,
9
+ el: HTMLElement,
10
+ ci: CommandInterface) {
11
+ let startedAt = -1;
12
+ let acc = 0;
13
+ let prevX = 0;
14
+ let prevY = 0;
15
+
16
+ const onMouseDown = (x: number, y: number) => {
17
+ startedAt = Date.now();
18
+ acc = 0;
19
+ prevX = x;
20
+ prevY = y;
21
+ };
22
+
23
+ function onMouseMove(x: number, y: number, mX: number, mY: number) {
24
+ if (mX === undefined) {
25
+ mX = x - prevX;
26
+ }
27
+
28
+ if (mY === undefined) {
29
+ mY = y - prevY;
30
+ }
31
+
32
+ prevX = x;
33
+ prevY = y;
34
+
35
+ if (mX === 0 && mY === 0) {
36
+ return;
37
+ }
38
+
39
+ acc += Math.abs(mX) + Math.abs(mY);
40
+
41
+ (ci as any).sendMouseRelativeMotion(mX * sensitivity * 2, mY * sensitivity * 2);
42
+ }
43
+
44
+ const onMouseUp = (x: number, y: number) => {
45
+ const delay = Date.now() - startedAt;
46
+
47
+ if (delay < clickDelay && acc < clickThreshold) {
48
+ const button = pointerButton ?? 0;
49
+ ci.sendMouseButton(button, true);
50
+ setTimeout(() => ci.sendMouseButton(button, false), 60);
51
+ }
52
+ };
53
+
54
+ const noop = () => {};
55
+
56
+ return mount(el, pointerButton, onMouseDown, onMouseMove, onMouseUp, noop);
57
+ }
@@ -0,0 +1,85 @@
1
+ function initBind() {
2
+ const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
3
+ const isTouch = isMobile && !!("ontouchstart" in window);
4
+ const isPointer = isMobile && (window.PointerEvent ? true : false);
5
+ const isMSPointer = isMobile && ((window as any).MSPointerEvent ? true : false);
6
+ let canLock = !isMobile;
7
+
8
+ const starters: string[] = [];
9
+ const changers: string[] = [];
10
+ const enders: string[] = [];
11
+ const leavers: string[] = [];
12
+ const prevents: string[] = [];
13
+
14
+ if (isPointer) {
15
+ starters.push("pointerdown");
16
+ enders.push("pointerup", "pointercancel");
17
+ changers.push("pointermove");
18
+ prevents.push("touchstart", "touchmove", "touchend");
19
+ } else if (isMSPointer) {
20
+ starters.push("MSPointerDown");
21
+ changers.push("MSPointerMove");
22
+ enders.push("MSPointerUp");
23
+ } else if (isTouch) {
24
+ canLock = false;
25
+ starters.push("touchstart", "mousedown");
26
+ changers.push("touchmove");
27
+ enders.push("touchend", "touchcancel", "mouseup");
28
+ } else {
29
+ starters.push("mousedown");
30
+ changers.push("mousemove");
31
+ enders.push("mouseup");
32
+ leavers.push("mouseleave");
33
+ }
34
+
35
+ return {
36
+ mobile: isMobile,
37
+ canLock,
38
+ starters,
39
+ changers,
40
+ enders,
41
+ prevents,
42
+ leavers,
43
+ };
44
+ }
45
+
46
+ export interface PointerState {
47
+ x: number,
48
+ y: number,
49
+ mX: number,
50
+ mY: number,
51
+ button?: number,
52
+ }
53
+
54
+ export function getPointerState(e: Event, el: HTMLElement): PointerState {
55
+ if (e.type.match(/^touch/)) {
56
+ const evt = e as TouchEvent;
57
+ const rect = el.getBoundingClientRect();
58
+ return {
59
+ x: evt.targetTouches[0].clientX - rect.x,
60
+ y: evt.targetTouches[0].clientY - rect.y,
61
+ mX: 0,
62
+ mY: 0,
63
+ };
64
+ } else if (e.type.match(/^pointer/)) {
65
+ const evt = e as PointerEvent;
66
+ return {
67
+ x: evt.offsetX,
68
+ y: evt.offsetY,
69
+ mX: evt.movementX,
70
+ mY: evt.movementY,
71
+ };
72
+ } else {
73
+ const evt = e as MouseEvent;
74
+ return {
75
+ x: evt.offsetX,
76
+ y: evt.offsetY,
77
+ mX: evt.movementX,
78
+ mY: evt.movementY,
79
+ button: evt.button === 0 ? 0 : 1,
80
+ };
81
+ }
82
+ }
83
+
84
+ export const pointer = initBind();
85
+
@@ -0,0 +1,21 @@
1
+ import { CommandInterface } from "emulators";
2
+ import { mouseCapture } from "./mouse/mouse-locked";
3
+ import { mouseDefault } from "./mouse/mouse-not-locked";
4
+ import { mouseSwipe } from "./mouse/mouse-swipe";
5
+ import { pointer } from "./mouse/pointer";
6
+
7
+ export function mouse(lock: boolean,
8
+ sensitivity: number,
9
+ pointerButton: number,
10
+ el: HTMLElement,
11
+ ci: CommandInterface) {
12
+ if (lock && !pointer.canLock) {
13
+ return mouseSwipe(sensitivity, pointerButton, el, ci);
14
+ }
15
+
16
+ if (lock) {
17
+ return mouseCapture(sensitivity, pointerButton, el, ci);
18
+ }
19
+
20
+ return mouseDefault(pointerButton, el, ci);
21
+ }
@@ -0,0 +1,191 @@
1
+ import { CommandInterface } from "emulators";
2
+ import { useEffect, useState } from "preact/hooks";
3
+ import { useSelector } from "react-redux";
4
+ import { State } from "../../store";
5
+
6
+ export const dhry2Bundle = "/b4b5275904d86a4ab8a20917b2b7e34f0df47bf7.jsdos";
7
+
8
+ export function Dhry2Results(props: { ci: CommandInterface }) {
9
+ const ci = props.ci;
10
+ const backend = useSelector((state: State) => state.dos.backend);
11
+ const worker = useSelector((state: State) => state.dos.worker);
12
+
13
+ const [results, setResults] = useState<{
14
+ runs: number,
15
+ time: number,
16
+ vax: number,
17
+ pc: string | null,
18
+ }>({
19
+ runs: 0,
20
+ time: 0,
21
+ vax: 0,
22
+ pc: null,
23
+ });
24
+
25
+ const [stats, setStats] = useState<{sleepPerSec: number, cyclesPerMs: number}>({
26
+ sleepPerSec: 0,
27
+ cyclesPerMs: 0,
28
+ });
29
+
30
+ useEffect(() => {
31
+ const listeners: ((message: string) => void)[] = [];
32
+
33
+ ci.events().onStdout((message: string) => {
34
+ for (const next of listeners) {
35
+ next(message);
36
+ }
37
+ });
38
+
39
+ const oldStdoutFn = ci.events().onStdout;
40
+ ci.events().onStdout = (fn: (message: string) => void) => {
41
+ listeners.push(fn);
42
+ };
43
+
44
+ // listen program outpus for `~>dtime` marker
45
+ let startedAt = Date.now();
46
+ let prevSleepCount = 0;
47
+ let prevCycles = 0;
48
+ ci.events().onStdout((message) => {
49
+ if (!message.startsWith("dhry2:")) {
50
+ return;
51
+ }
52
+
53
+ ci.asyncifyStats().then((stats) => {
54
+ const dt = Date.now() - startedAt;
55
+ setStats({
56
+ sleepPerSec: Math.round((stats.sleepCount - prevSleepCount) * 1000 / dt),
57
+ cyclesPerMs: Math.round((stats.cycles - prevCycles) / dt),
58
+ });
59
+ startedAt = Date.now();
60
+ prevSleepCount = stats.sleepCount;
61
+ prevCycles = stats.cycles;
62
+ });
63
+
64
+ // eslint-disable-next-line
65
+ const [_, runsStr, deltaStr, vaxRatingStr] = message.split(" ");
66
+ const runs = Number.parseInt(runsStr);
67
+ const delta = Number.parseFloat(deltaStr);
68
+ const vaxRating = Number.parseFloat(vaxRatingStr);
69
+
70
+ if (delta >= 5000) {
71
+ setResults({
72
+ runs,
73
+ time: Math.round(delta * 10) / 10,
74
+ vax: Math.round(vaxRating * 100) / 100,
75
+ pc: getComparablePc(vaxRating),
76
+ });
77
+ } else {
78
+ setResults({
79
+ runs,
80
+ time: Math.round(delta * 10) / 10,
81
+ vax: Math.round(vaxRating * 100) / 100,
82
+ pc: null,
83
+ });
84
+ }
85
+ });
86
+
87
+ return () => {
88
+ ci.events().onStdout = oldStdoutFn;
89
+ };
90
+ }, [ci]);
91
+
92
+ return <div class="dhry2-window">
93
+ <div class="title">Dhrystone 2 Benchmark</div>
94
+ <div class="backend">Backend: {backend + "-" + (worker ? "worker" : "direct")}</div>
95
+ <div class="results">
96
+ <div>VAX:</div>
97
+ <div>{results.vax}</div>
98
+ <div>Cycles p/ms:</div>
99
+ <div>{stats.cyclesPerMs}</div>
100
+ <div>Sleep p/sec:</div>
101
+ <div>{stats.sleepPerSec}</div>
102
+ <div>Runs:</div>
103
+ <div>{results.runs}</div>
104
+ <div>Time:</div>
105
+ <div>{results.time} <span>ms</span></div>
106
+ {results.pc !== null && <div>PC:</div>}
107
+ {results.pc !== null && <div>{results.pc ?? "..."}</div>}
108
+ </div>
109
+ </div>;
110
+ }
111
+
112
+
113
+ function getComparablePc(vax: number): string {
114
+ let index = 0;
115
+ while ((index * 2 + 1) < results.length) {
116
+ if (results[index * 2 + 1] as number >= vax) {
117
+ break;
118
+ }
119
+
120
+ index++;
121
+ }
122
+
123
+ index = Math.min((results.length / 2 - 1), index);
124
+ return (results[index * 2] + "").trim();
125
+ }
126
+
127
+ // Reuslts from
128
+ // http://www.roylongbottom.org.uk/dhrystone%20results.htm
129
+ const results = [
130
+ "AMD 80386 40MHz", 13.7,
131
+ "IBM 486D2 50MHz", 22.4,
132
+ "80486 DX2 66MHz", 35.3,
133
+ "IBM 486BL 100MHz", 40.9,
134
+ "AMD 5X86 133MHz", 84.5,
135
+ "Pentium 75MHz", 87.1,
136
+ "Cyrix P150 120MHz", 160,
137
+ "Pentium 100MHz", 122,
138
+ "Cyrix PP166 133MHz", 180,
139
+ "IBM 6x86 150MHz", 188,
140
+ "Pentium 133MHz", 181,
141
+ "Pentium 166MHz", 189,
142
+ "Cyrix PR233 188MHz", 232,
143
+ "Pentium 200MHz", 269,
144
+ "Pentium MMX 200MHz", 276,
145
+ "AMD K6 200MHz", 289,
146
+ "Pentium Pro 200MHz", 312,
147
+ "Celeron A 300MHz", 484,
148
+ "Pentium II 300MHz", 477,
149
+ "AMD K62 500MHz", 606,
150
+ "AMD K63 450MHz", 645,
151
+ "Pentium II 450MHz", 713,
152
+ "Celeron A 450MHz", 720,
153
+ "Pentium III 450MHz", 722,
154
+ "Pentium III 600MHz", 959,
155
+ "Athlon 600MHz", 942,
156
+ "Duron 600MHz", 999,
157
+ "Pentium III 1000MHz", 1595,
158
+ "PIII Tualatin 1200MHz", 1907,
159
+ "Pentium 4 1700MHz", 1843,
160
+ "Athlon Tbird 1000MHz", 1659,
161
+ "Duron 1000MHz", 1674,
162
+ "Celeron M 1295MHz", 2273,
163
+ "Atom 1600MHz", 1828,
164
+ "Pentium 4 1900MHz", 2003,
165
+ "Atom 1666MHz", 1948,
166
+ "P4 Xeon 2200MHz", 2265,
167
+ "Atom Z8300 1840MHz", 2686,
168
+ "Athlon 4 1600MHz", 2830,
169
+ "Pentium M 1862MHz", 3933,
170
+ "Ath4 Barton 1800MHz", 3172,
171
+ "Pentium 4E 3000MHz", 3553,
172
+ "Athlon XP 2080MHz", 3700,
173
+ "Turion 64 M 1900MHz", 3742,
174
+ "Pentium 4 3066MHz", 4012,
175
+ "Opteron 1991MHz", 3985,
176
+ "Core 2 Duo M 1830MHz", 4952,
177
+ "Athlon XP 2338MHz", 4160,
178
+ "Athlon 64 2150MHz", 4288,
179
+ "Pentium 4 3678MHz", 4227,
180
+ "Athlon 64 2211MHz", 4462,
181
+ "Celeron C2 M 2000MHz", 5275,
182
+ "Core 2 Duo 1 CP 2400MHz", 6446,
183
+ "Core i5 2467M 2300MHz", 4752,
184
+ "Phenom II 1 CP 3000MHz", 7615,
185
+ "Core i7 930 3066MHz", 8684,
186
+ "Core i7 860 3460MHz", 9978,
187
+ "Core i7 3930K 3800MHz", 11197,
188
+ "Core i7 4820K 3900MHz", 11867,
189
+ "Core i7 4820K 3900MHz", 11978,
190
+ "Core i7 3930K", 13877,
191
+ ];
@@ -0,0 +1,185 @@
1
+ import { useDispatch, useSelector } from "react-redux";
2
+ import { dosSlice, FitConstant } from "../../store/dos";
3
+ import { State, store } from "../../store";
4
+ import { CommandInterface } from "emulators";
5
+ import { keyboard } from "./controls/keyboard";
6
+ import { webGl as webglRender } from "./render/webgl";
7
+ import { canvas as canvasRender } from "./render/canvas";
8
+ import { audioNode } from "./sound/audio-node";
9
+ import { useEffect } from "preact/hooks";
10
+ import { mouse } from "./controls/mouse";
11
+ import { KBD_0 } from "./controls/keys";
12
+ import { uiSlice } from "../../store/ui";
13
+
14
+ export function useDosRuntime(canvas: HTMLCanvasElement,
15
+ ci: CommandInterface): void {
16
+ useLog(ci);
17
+ useRenderImage(canvas);
18
+ useStats(ci);
19
+ usePause(ci);
20
+ useKeyboard(ci);
21
+ useMouse(canvas, ci);
22
+ useRenderBackend(canvas, ci);
23
+ useAudioBackend(ci);
24
+ }
25
+
26
+ function useLog(ci: CommandInterface): void {
27
+ const dispatch = useDispatch();
28
+ useEffect(() => {
29
+ ci.events().onMessage((msgType, ...args) => {
30
+ if (msgType === "error" && args[0]?.startsWith("[panic]")) {
31
+ dispatch(uiSlice.actions.showToast({
32
+ message: args[0],
33
+ intent: "panic",
34
+ }));
35
+ } else if (msgType === "log" && args[0]?.indexOf("sockdrive:") !== -1) {
36
+ dispatch(uiSlice.actions.cloudSaves(false));
37
+ if (args[0]?.indexOf("write=false") !== -1) {
38
+ dispatch(uiSlice.actions.readOnlyWarning(true));
39
+ }
40
+ }
41
+ });
42
+ }, [ci, dispatch]);
43
+ }
44
+
45
+ function useRenderImage(canvas: HTMLCanvasElement): void {
46
+ const rendering = useSelector((state: State) => state.dos.imageRendering);
47
+ useEffect(() => {
48
+ canvas.style.imageRendering = rendering === "smooth" ? "auto" : rendering;
49
+ }, [canvas, rendering]);
50
+ }
51
+
52
+ function useMouse(canvas: HTMLCanvasElement,
53
+ ci: CommandInterface): void {
54
+ const mouseCapture = useSelector((state: State) => state.dos.mouseCapture);
55
+ const mouseSensitivity = useSelector((state: State) => state.dos.mouseSensitivity);
56
+ useEffect(() => {
57
+ const sensistiviy = 0.1 + mouseSensitivity * 3;
58
+ return mouse(mouseCapture, sensistiviy, 0, canvas, ci);
59
+ }, [canvas, ci, mouseCapture, mouseSensitivity]);
60
+ }
61
+
62
+ function useKeyboard(ci: CommandInterface): void {
63
+ useEffect(() => {
64
+ return keyboard(window as any, ci);
65
+ }, [ci]);
66
+ }
67
+
68
+ function useRenderBackend(canvas: HTMLCanvasElement,
69
+ ci: CommandInterface): void {
70
+ const renderBackend = useSelector((state: State) => state.dos.renderBackend);
71
+ const renderAspect = useSelector((state: State) => state.dos.renderAspect);
72
+
73
+ let aspect: number | undefined = undefined;
74
+ switch (renderAspect) {
75
+ case "1/1": aspect = 1; break;
76
+ case "5/4": aspect = 5 / 4; break;
77
+ case "4/3": aspect = 4 / 3; break;
78
+ case "16/10": aspect = 16 / 10; break;
79
+ case "16/9": aspect = 16 / 9; break;
80
+ case "Fit": aspect = FitConstant;
81
+ default:
82
+ }
83
+
84
+ useEffect(() => {
85
+ let unbind = () => { };
86
+
87
+ if (renderBackend === "canvas") {
88
+ unbind = canvasRender(canvas, ci, aspect);
89
+ } else {
90
+ try {
91
+ unbind = webglRender(canvas, ci, aspect);
92
+ } catch (e) {
93
+ console.error("Unalbe to start webgl render", e);
94
+ unbind = canvasRender(canvas, ci, aspect);
95
+ }
96
+ }
97
+
98
+ return unbind;
99
+ }, [canvas, ci, renderBackend, aspect]);
100
+ }
101
+
102
+ function useAudioBackend(ci: CommandInterface): void {
103
+ useEffect(() => {
104
+ const unbind = audioNode(ci, (setVolume) => {
105
+ let volume = 1;
106
+
107
+ const updateVolumeIfNeeded = () => {
108
+ const newVolume = store.getState().dos.volume;
109
+ if (Math.abs(volume - newVolume) >= 0.05) {
110
+ setVolume(newVolume);
111
+ volume = newVolume;
112
+ }
113
+ };
114
+
115
+ const unsubscribe = store.subscribe(updateVolumeIfNeeded);
116
+ return unsubscribe;
117
+ });
118
+
119
+ return unbind;
120
+ }, [ci]);
121
+ }
122
+
123
+ function usePause(ci: CommandInterface): void {
124
+ const paused = useSelector((state: State) => state.dos.paused);
125
+ useEffect(() => {
126
+ paused ? ci.pause() : ci.resume();
127
+ }, [paused, ci]);
128
+ }
129
+
130
+ function useStats(ci: CommandInterface): void {
131
+ const dispatch = useDispatch();
132
+ useEffect(() => {
133
+ let prevCycles = 0;
134
+ let prevNonSkippableSleepCount = 0;
135
+ let prevSleepCount = 0;
136
+ let prevSleepTime = KBD_0;
137
+ let prevFrame = 0;
138
+ let prevSound = 0;
139
+ let prevMsgSent = 0;
140
+ let prevMsgRecv = 0;
141
+ let intervalStartedAt = Date.now();
142
+ const intervalId = setInterval(() => {
143
+ ci.asyncifyStats().then((stats) => {
144
+ const dtMs = Date.now() - intervalStartedAt;
145
+ const dtSec = dtMs / 1000;
146
+ if (dtSec > 0) {
147
+ const dStats = {
148
+ cyclesPerMs: Math.round((stats.cycles - prevCycles) / dtMs),
149
+ nonSkippableSleepPreSec: Math.round((stats.nonSkippableSleepCount -
150
+ prevNonSkippableSleepCount) / dtSec),
151
+ sleepPerSec: Math.round((stats.sleepCount - prevSleepCount) / dtSec),
152
+ sleepTimePerSec: Math.round((stats.sleepTime - prevSleepTime) / dtSec),
153
+ framePerSec: Math.round((stats.messageFrame - prevFrame) / dtSec),
154
+ soundPerSec: Math.round((stats.messageSound - prevSound) / dtSec),
155
+ msgSentPerSec: Math.round((stats.messageSent - prevMsgSent) / dtSec),
156
+ msgRecvPerSec: Math.round((stats.messageReceived - prevMsgRecv) / dtSec),
157
+ netSent: stats.netSent,
158
+ netRecv: stats.netRecv,
159
+ driveSent: stats.driveSent,
160
+ driveRecv: stats.driveRecv,
161
+ driveRecvTime: stats.driveRecvTime,
162
+ driveCacheHit: stats.driveCacheHit,
163
+ driveCacheMiss: stats.driveCacheMiss,
164
+ driveCacheUsed: stats.driveCacheUsed,
165
+ };
166
+ dispatch(dosSlice.actions.stats(dStats));
167
+
168
+ prevCycles = stats.cycles;
169
+ prevNonSkippableSleepCount = stats.nonSkippableSleepCount;
170
+ prevSleepCount = stats.sleepCount;
171
+ prevSleepTime = stats.sleepTime;
172
+ prevFrame = stats.messageFrame;
173
+ prevSound = stats.messageSound;
174
+ prevMsgSent = stats.messageSent;
175
+ prevMsgRecv = stats.messageReceived;
176
+ intervalStartedAt = Date.now();
177
+ }
178
+ });
179
+ }, 3000);
180
+
181
+ return () => {
182
+ clearInterval(intervalId);
183
+ };
184
+ }, [dispatch, ci]);
185
+ }