rk86 2.0.6 → 2.0.8

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 (2) hide show
  1. package/package.json +1 -1
  2. package/rk86.js +69 -149
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rk86",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "description": "Эмулятор Радио-86РК (Intel 8080) для терминала",
5
5
  "bin": {
6
6
  "rk86": "rk86.js"
package/rk86.js CHANGED
@@ -2468,6 +2468,18 @@ var extract_rk86_word = function(v, i) {
2468
2468
  };
2469
2469
  var to_text = (binary) => binary.reduce((a, x) => a + String.fromCharCode(x), "");
2470
2470
  var is_hex_file = (image) => to_text(image.slice(0, 6)) === "#!rk86";
2471
+ var parse = (binary) => {
2472
+ try {
2473
+ if (!binary)
2474
+ return { ok: false };
2475
+ if (binary instanceof Uint8Array)
2476
+ binary = Array.from(binary);
2477
+ const text = to_text(binary);
2478
+ return { ok: true, json: JSON.parse(text) };
2479
+ } catch {
2480
+ return { ok: false };
2481
+ }
2482
+ };
2471
2483
  var convert_hex_to_binary = function(text) {
2472
2484
  const lines = text.split(`
2473
2485
  `).filter((line) => line.trim().length).filter((line) => !line.startsWith(";") && !line.startsWith("#"));
@@ -2659,7 +2671,7 @@ function create(array, width = 16) {
2659
2671
  }
2660
2672
  return v;
2661
2673
  }
2662
- function parse(hex2) {
2674
+ function parse2(hex2) {
2663
2675
  const array = [];
2664
2676
  for (let [label, line] of Object.entries(hex2)) {
2665
2677
  const address = parseInt(label.slice(1), 16);
@@ -2773,7 +2785,7 @@ class Memory {
2773
2785
  this.video_screen_cursor_y = snapshot.video_screen_cursor_y;
2774
2786
  this.last_access_address = h(snapshot.last_access_address);
2775
2787
  this.last_access_operation = snapshot.last_access_operation;
2776
- this.buf = parse(snapshot.memory);
2788
+ this.buf = parse2(snapshot.memory);
2777
2789
  };
2778
2790
  invalidate_access_variables() {
2779
2791
  this.last_access_address = 0;
@@ -3103,11 +3115,6 @@ class Screen {
3103
3115
  static #update_rate = 25;
3104
3116
  machine;
3105
3117
  cursor_rate;
3106
- char_width;
3107
- char_height;
3108
- char_height_gap;
3109
- cursor_width;
3110
- cursor_height;
3111
3118
  scale_x;
3112
3119
  scale_y;
3113
3120
  width;
@@ -3115,22 +3122,15 @@ class Screen {
3115
3122
  cursor_state;
3116
3123
  cursor_x;
3117
3124
  cursor_y;
3118
- last_cursor_state;
3119
- last_cursor_x;
3120
- last_cursor_y;
3121
- font;
3122
3125
  light_pen_x;
3123
3126
  light_pen_y;
3124
3127
  light_pen_active;
3125
- ctx;
3128
+ video_memory_base = 0;
3129
+ video_memory_size = 0;
3130
+ renderer;
3126
3131
  constructor(machine) {
3127
3132
  this.machine = machine;
3128
3133
  this.cursor_rate = 500;
3129
- this.char_width = 6;
3130
- this.char_height = 8;
3131
- this.char_height_gap = 2;
3132
- this.cursor_width = this.char_width;
3133
- this.cursor_height = 1;
3134
3134
  this.scale_x = 1;
3135
3135
  this.scale_y = 1;
3136
3136
  this.width = 78;
@@ -3138,11 +3138,6 @@ class Screen {
3138
3138
  this.cursor_state = false;
3139
3139
  this.cursor_x = 0;
3140
3140
  this.cursor_y = 0;
3141
- this.last_cursor_state = false;
3142
- this.last_cursor_x = 0;
3143
- this.last_cursor_y = 0;
3144
- this.font = new Image;
3145
- this.font.src = this.machine.font;
3146
3141
  this.light_pen_x = 0;
3147
3142
  this.light_pen_y = 0;
3148
3143
  this.light_pen_active = 0;
@@ -3183,73 +3178,32 @@ class Screen {
3183
3178
  this.set_geometry(this.width, this.height);
3184
3179
  this.set_video_memory(this.video_memory_base);
3185
3180
  }
3186
- start() {
3187
- this.init();
3188
- this.draw_screen();
3181
+ start(renderer) {
3182
+ this.renderer = renderer;
3183
+ this.renderer.connect(this.machine);
3189
3184
  this.flip_cursor();
3190
- this.machine.ui.canvas.onmousemove = this.handle_mousemove.bind(this);
3191
- this.machine.ui.canvas.onmouseup = () => this.light_pen_active = 0;
3192
- this.machine.ui.canvas.onmousedown = () => this.light_pen_active = 1;
3193
- }
3194
- cache = [];
3195
- init_cache(sz) {
3196
- for (let i = 0;i < sz; ++i)
3197
- this.cache[i] = -1;
3198
- }
3199
- draw_char(x, y, ch) {
3200
- this.ctx.drawImage(this.font, 2, this.char_height * ch, this.char_width, this.char_height, x * this.char_width * this.scale_x, y * (this.char_height + this.char_height_gap) * this.scale_y, this.char_width * this.scale_x, this.char_height * this.scale_y);
3201
- }
3202
- draw_cursor(x, y, visible) {
3203
- const cy = (y2) => (y2 * (this.char_height + this.char_height_gap) + this.char_height) * this.scale_y;
3204
- if (this.last_cursor_x !== x || this.last_cursor_y !== y) {
3205
- if (this.last_cursor_state) {
3206
- this.ctx.fillStyle = "#000000";
3207
- this.ctx.fillRect(this.last_cursor_x * this.char_width * this.scale_x, cy(this.last_cursor_y), this.cursor_width * this.scale_x, this.cursor_height * this.scale_y);
3208
- }
3209
- this.last_cursor_state = this.cursor_state;
3210
- this.last_cursor_x = x;
3211
- this.last_cursor_y = y;
3212
- }
3213
- const cx = x * this.char_width * this.scale_x;
3214
- this.ctx.fillStyle = visible ? "#ffffff" : "#000000";
3215
- this.ctx.fillRect(cx, cy(y), this.cursor_width * this.scale_x, this.cursor_height * this.scale_y);
3185
+ this.render_loop();
3216
3186
  }
3217
- flip_cursor() {
3218
- this.draw_cursor(this.cursor_x, this.cursor_y, this.cursor_state);
3219
- this.cursor_state = !this.cursor_state;
3220
- setTimeout(() => this.flip_cursor(), this.cursor_rate);
3221
- }
3222
- init() {
3223
- this.ctx = this.machine.ui.canvas.getContext("2d");
3224
- }
3225
- disable_smoothing() {
3226
- this.ctx.imageSmoothingEnabled = false;
3187
+ render_loop() {
3188
+ this.renderer.update();
3189
+ setTimeout(() => this.render_loop(), Screen.#update_rate);
3227
3190
  }
3228
3191
  last_width = 0;
3229
3192
  last_height = 0;
3230
- video_memory_size = 0;
3231
3193
  set_geometry(width, height) {
3232
3194
  this.width = width;
3233
3195
  this.height = height;
3234
3196
  this.video_memory_size = width * height;
3235
3197
  this.machine.ui.update_screen_geometry(this.width, this.height);
3236
- const canvas_width = this.width * this.char_width * this.scale_x;
3237
- const canvas_height = this.height * (this.char_height + this.char_height_gap) * this.scale_y;
3238
- this.machine.ui.resize_canvas(canvas_width, canvas_height);
3239
- this.disable_smoothing();
3240
- this.ctx.fillStyle = "#000000";
3241
- this.ctx.fillRect(0, 0, canvas_width, canvas_height);
3242
3198
  if (this.last_width === this.width && this.last_height === this.height)
3243
3199
  return;
3244
3200
  console.log(`\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0440\u0430\u0437\u043C\u0435\u0440 \u044D\u043A\u0440\u0430\u043D\u0430: ${width} x ${height}`);
3245
3201
  this.last_width = this.width;
3246
3202
  this.last_height = this.height;
3247
3203
  }
3248
- video_memory_base = 0;
3249
3204
  last_video_memory_base = 0;
3250
3205
  set_video_memory(base) {
3251
3206
  this.video_memory_base = base;
3252
- this.init_cache(this.video_memory_size);
3253
3207
  this.machine.ui.update_video_memory_address(this.video_memory_base);
3254
3208
  if (this.last_video_memory_base === this.video_memory_base)
3255
3209
  return;
@@ -3257,37 +3211,35 @@ class Screen {
3257
3211
  this.last_video_memory_base = this.video_memory_base;
3258
3212
  }
3259
3213
  set_cursor(x, y) {
3260
- this.draw_cursor(this.cursor_x, this.cursor_y, false);
3261
3214
  this.cursor_x = x;
3262
3215
  this.cursor_y = y;
3263
3216
  }
3264
- draw_screen() {
3265
- const memory = this.machine.memory;
3266
- let i = this.video_memory_base;
3267
- for (let y = 0;y < this.height; ++y) {
3268
- for (let x = 0;x < this.width; ++x) {
3269
- const cache_i = i - this.video_memory_base;
3270
- const ch = memory.read(i);
3271
- if (this.cache[cache_i] !== ch) {
3272
- this.draw_char(x, y, ch);
3273
- this.cache[cache_i] = ch;
3274
- }
3275
- i += 1;
3276
- }
3277
- }
3278
- setTimeout(() => this.draw_screen(), Screen.#update_rate);
3279
- }
3280
- handle_mousemove(event) {
3281
- const canvas = this.machine.ui.canvas;
3282
- const box = canvas.getBoundingClientRect();
3283
- const scaleX = canvas.width / box.width;
3284
- const scaleY = canvas.height / box.height;
3285
- const mouseX = (event.clientX - box.left) * scaleX;
3286
- const mouseY = (event.clientY - box.top) * scaleY;
3287
- const x = Math.floor(mouseX / (this.char_width * this.scale_x));
3288
- const y = Math.floor(mouseY / ((this.char_height + this.char_height_gap) * this.scale_y));
3289
- this.light_pen_x = x;
3290
- this.light_pen_y = y;
3217
+ flip_cursor() {
3218
+ this.cursor_state = !this.cursor_state;
3219
+ setTimeout(() => this.flip_cursor(), this.cursor_rate);
3220
+ }
3221
+ }
3222
+
3223
+ // src/lib/rk86_snapshot.ts
3224
+ function rk86_snapshot_restore(snapshot, machine, keys_injector) {
3225
+ try {
3226
+ const json = typeof snapshot === "string" ? JSON.parse(snapshot) : snapshot;
3227
+ if (json.id != "rk86")
3228
+ return false;
3229
+ if (!machine)
3230
+ return false;
3231
+ const { screen, cpu, memory, keyboard } = machine;
3232
+ cpu.import(json.cpu);
3233
+ keyboard.import(json.keyboard);
3234
+ screen.import(json.screen);
3235
+ memory.import(json.memory);
3236
+ screen.apply_import();
3237
+ if (keys_injector && json.boot?.keyboard)
3238
+ keys_injector(json.boot?.keyboard);
3239
+ return true;
3240
+ } catch (e) {
3241
+ console.error("failed restoring snapshot", e);
3242
+ return false;
3291
3243
  }
3292
3244
  }
3293
3245
 
@@ -3393,8 +3345,6 @@ class Tape {
3393
3345
  }
3394
3346
 
3395
3347
  // src/lib/rk86_terminal.ts
3396
- globalThis.Image = class {
3397
- };
3398
3348
  var charMap = {
3399
3349
  0: " ",
3400
3350
  1: "\u2598",
@@ -3560,35 +3510,25 @@ class IO {
3560
3510
  interrupt = (_iff) => {};
3561
3511
  }
3562
3512
 
3563
- class TerminalScreen {
3513
+ class TerminalRenderer {
3564
3514
  machine;
3565
- width = 78;
3566
- height = 30;
3567
- video_memory_base = 0;
3568
- timer;
3569
- constructor(machine) {
3515
+ connect(machine) {
3570
3516
  this.machine = machine;
3571
3517
  }
3572
- start() {
3573
- this.render();
3574
- }
3575
- render() {
3518
+ update() {
3576
3519
  const { memory, screen } = this.machine;
3577
- const cursorX = screen.cursor_x;
3578
- const cursorY = screen.cursor_y;
3579
- const cursorVisible = screen.cursor_state;
3580
3520
  const dim = "\x1B[2m";
3581
3521
  const reset = "\x1B[0m";
3582
- const w = this.width;
3522
+ const w = screen.width;
3583
3523
  let output = "\x1B[H";
3584
3524
  output += `${dim}\u250C${"\u2500".repeat(w)}\u2510${reset}
3585
3525
  `;
3586
- let addr = this.video_memory_base;
3587
- for (let y = 0;y < this.height; y++) {
3526
+ let addr = screen.video_memory_base;
3527
+ for (let y = 0;y < screen.height; y++) {
3588
3528
  let line = `${dim}\u2502${reset}`;
3589
3529
  for (let x = 0;x < w; x++) {
3590
3530
  const ch = rk86char(memory.read(addr));
3591
- if (x === cursorX && y === cursorY) {
3531
+ if (x === screen.cursor_x && y === screen.cursor_y) {
3592
3532
  line += `\x1B[4m${ch}${reset}`;
3593
3533
  } else {
3594
3534
  line += ch;
@@ -3602,7 +3542,6 @@ class TerminalScreen {
3602
3542
  output += `${dim}\u2514${"\u2500".repeat(w)}\u2518${reset}
3603
3543
  `;
3604
3544
  process.stdout.write(output);
3605
- this.timer = setTimeout(() => this.render(), 40);
3606
3545
  }
3607
3546
  }
3608
3547
  var KEY_MAP = {
@@ -3780,42 +3719,23 @@ async function main() {
3780
3719
  let entryPoint;
3781
3720
  if (programFile) {
3782
3721
  const content = await fetchFile(programFile);
3783
- const file = parse_rk86_binary(programFile, content);
3784
- machine.memory.load_file(file);
3785
- entryPoint = file.entry;
3786
- console.error(`\u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D: ${programFile} (${file.start.toString(16)}-${file.end.toString(16)}, G${file.entry.toString(16)})`);
3722
+ const { ok, json } = parse(content);
3723
+ if (ok) {
3724
+ rk86_snapshot_restore(json, machine);
3725
+ entryPoint = parseInt(json.cpu.pc);
3726
+ console.error(`\u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D \u043E\u0431\u0440\u0430\u0437: ${programFile} (PC=${entryPoint.toString(16)})`);
3727
+ } else {
3728
+ const file = parse_rk86_binary(programFile, content);
3729
+ machine.memory.load_file(file);
3730
+ entryPoint = file.entry;
3731
+ console.error(`\u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D: ${programFile} (${file.start.toString(16)}-${file.end.toString(16)}, G${file.entry.toString(16)})`);
3732
+ }
3787
3733
  }
3788
3734
  process.stdout.write("\x1B[?25l");
3789
3735
  process.stdout.write("\x1B[2J");
3790
3736
  setupKeyboard(keyboard);
3791
- const termScreen = new TerminalScreen(machine);
3792
- const origSetGeometry = machine.screen.set_geometry.bind(machine.screen);
3793
- machine.screen.set_geometry = (width, height) => {
3794
- origSetGeometry(width, height);
3795
- termScreen.width = width;
3796
- termScreen.height = height;
3797
- };
3798
- const origSetVideoMemory = machine.screen.set_video_memory.bind(machine.screen);
3799
- machine.screen.set_video_memory = (base) => {
3800
- origSetVideoMemory(base);
3801
- termScreen.video_memory_base = base;
3802
- };
3803
- const noopCtx = {
3804
- imageSmoothingEnabled: false,
3805
- fillStyle: "",
3806
- fillRect() {},
3807
- drawImage() {},
3808
- clearRect() {}
3809
- };
3810
- machine.screen.ctx = noopCtx;
3811
- machine.screen.init = () => {
3812
- machine.screen.ctx = noopCtx;
3813
- };
3814
- machine.screen.draw_screen = () => {};
3815
- machine.screen.draw_cursor = () => {};
3816
- machine.screen.start();
3737
+ machine.screen.start(new TerminalRenderer);
3817
3738
  machine.runner.execute();
3818
- termScreen.start();
3819
3739
  if (entryPoint !== undefined && !loadOnly) {
3820
3740
  setTimeout(() => machine.cpu.jump(entryPoint), 500);
3821
3741
  }