rk86 2.0.5 → 2.0.7

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 +32 -164
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rk86",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "Эмулятор Радио-86РК (Intel 8080) для терминала",
5
5
  "bin": {
6
6
  "rk86": "rk86.js"
package/rk86.js CHANGED
@@ -1588,6 +1588,10 @@ var init_catalog_data = __esm(() => {
1588
1588
  ];
1589
1589
  });
1590
1590
 
1591
+ // src/lib/rk86_terminal.ts
1592
+ import { existsSync } from "fs";
1593
+ import { readFile } from "fs/promises";
1594
+
1591
1595
  // src/lib/hex.ts
1592
1596
  function hex(v, prefix) {
1593
1597
  return v.toString(16).toUpperCase();
@@ -3099,11 +3103,6 @@ class Screen {
3099
3103
  static #update_rate = 25;
3100
3104
  machine;
3101
3105
  cursor_rate;
3102
- char_width;
3103
- char_height;
3104
- char_height_gap;
3105
- cursor_width;
3106
- cursor_height;
3107
3106
  scale_x;
3108
3107
  scale_y;
3109
3108
  width;
@@ -3111,22 +3110,15 @@ class Screen {
3111
3110
  cursor_state;
3112
3111
  cursor_x;
3113
3112
  cursor_y;
3114
- last_cursor_state;
3115
- last_cursor_x;
3116
- last_cursor_y;
3117
- font;
3118
3113
  light_pen_x;
3119
3114
  light_pen_y;
3120
3115
  light_pen_active;
3121
- ctx;
3116
+ video_memory_base = 0;
3117
+ video_memory_size = 0;
3118
+ renderer;
3122
3119
  constructor(machine) {
3123
3120
  this.machine = machine;
3124
3121
  this.cursor_rate = 500;
3125
- this.char_width = 6;
3126
- this.char_height = 8;
3127
- this.char_height_gap = 2;
3128
- this.cursor_width = this.char_width;
3129
- this.cursor_height = 1;
3130
3122
  this.scale_x = 1;
3131
3123
  this.scale_y = 1;
3132
3124
  this.width = 78;
@@ -3134,11 +3126,6 @@ class Screen {
3134
3126
  this.cursor_state = false;
3135
3127
  this.cursor_x = 0;
3136
3128
  this.cursor_y = 0;
3137
- this.last_cursor_state = false;
3138
- this.last_cursor_x = 0;
3139
- this.last_cursor_y = 0;
3140
- this.font = new Image;
3141
- this.font.src = this.machine.font;
3142
3129
  this.light_pen_x = 0;
3143
3130
  this.light_pen_y = 0;
3144
3131
  this.light_pen_active = 0;
@@ -3179,73 +3166,32 @@ class Screen {
3179
3166
  this.set_geometry(this.width, this.height);
3180
3167
  this.set_video_memory(this.video_memory_base);
3181
3168
  }
3182
- start() {
3183
- this.init();
3184
- this.draw_screen();
3169
+ start(renderer) {
3170
+ this.renderer = renderer;
3171
+ this.renderer.connect(this.machine);
3185
3172
  this.flip_cursor();
3186
- this.machine.ui.canvas.onmousemove = this.handle_mousemove.bind(this);
3187
- this.machine.ui.canvas.onmouseup = () => this.light_pen_active = 0;
3188
- this.machine.ui.canvas.onmousedown = () => this.light_pen_active = 1;
3189
- }
3190
- cache = [];
3191
- init_cache(sz) {
3192
- for (let i = 0;i < sz; ++i)
3193
- this.cache[i] = -1;
3194
- }
3195
- draw_char(x, y, ch) {
3196
- 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);
3197
- }
3198
- draw_cursor(x, y, visible) {
3199
- const cy = (y2) => (y2 * (this.char_height + this.char_height_gap) + this.char_height) * this.scale_y;
3200
- if (this.last_cursor_x !== x || this.last_cursor_y !== y) {
3201
- if (this.last_cursor_state) {
3202
- this.ctx.fillStyle = "#000000";
3203
- 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);
3204
- }
3205
- this.last_cursor_state = this.cursor_state;
3206
- this.last_cursor_x = x;
3207
- this.last_cursor_y = y;
3208
- }
3209
- const cx = x * this.char_width * this.scale_x;
3210
- this.ctx.fillStyle = visible ? "#ffffff" : "#000000";
3211
- this.ctx.fillRect(cx, cy(y), this.cursor_width * this.scale_x, this.cursor_height * this.scale_y);
3173
+ this.render_loop();
3212
3174
  }
3213
- flip_cursor() {
3214
- this.draw_cursor(this.cursor_x, this.cursor_y, this.cursor_state);
3215
- this.cursor_state = !this.cursor_state;
3216
- setTimeout(() => this.flip_cursor(), this.cursor_rate);
3217
- }
3218
- init() {
3219
- this.ctx = this.machine.ui.canvas.getContext("2d");
3220
- }
3221
- disable_smoothing() {
3222
- this.ctx.imageSmoothingEnabled = false;
3175
+ render_loop() {
3176
+ this.renderer.update();
3177
+ setTimeout(() => this.render_loop(), Screen.#update_rate);
3223
3178
  }
3224
3179
  last_width = 0;
3225
3180
  last_height = 0;
3226
- video_memory_size = 0;
3227
3181
  set_geometry(width, height) {
3228
3182
  this.width = width;
3229
3183
  this.height = height;
3230
3184
  this.video_memory_size = width * height;
3231
3185
  this.machine.ui.update_screen_geometry(this.width, this.height);
3232
- const canvas_width = this.width * this.char_width * this.scale_x;
3233
- const canvas_height = this.height * (this.char_height + this.char_height_gap) * this.scale_y;
3234
- this.machine.ui.resize_canvas(canvas_width, canvas_height);
3235
- this.disable_smoothing();
3236
- this.ctx.fillStyle = "#000000";
3237
- this.ctx.fillRect(0, 0, canvas_width, canvas_height);
3238
3186
  if (this.last_width === this.width && this.last_height === this.height)
3239
3187
  return;
3240
3188
  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}`);
3241
3189
  this.last_width = this.width;
3242
3190
  this.last_height = this.height;
3243
3191
  }
3244
- video_memory_base = 0;
3245
3192
  last_video_memory_base = 0;
3246
3193
  set_video_memory(base) {
3247
3194
  this.video_memory_base = base;
3248
- this.init_cache(this.video_memory_size);
3249
3195
  this.machine.ui.update_video_memory_address(this.video_memory_base);
3250
3196
  if (this.last_video_memory_base === this.video_memory_base)
3251
3197
  return;
@@ -3253,37 +3199,12 @@ class Screen {
3253
3199
  this.last_video_memory_base = this.video_memory_base;
3254
3200
  }
3255
3201
  set_cursor(x, y) {
3256
- this.draw_cursor(this.cursor_x, this.cursor_y, false);
3257
3202
  this.cursor_x = x;
3258
3203
  this.cursor_y = y;
3259
3204
  }
3260
- draw_screen() {
3261
- const memory = this.machine.memory;
3262
- let i = this.video_memory_base;
3263
- for (let y = 0;y < this.height; ++y) {
3264
- for (let x = 0;x < this.width; ++x) {
3265
- const cache_i = i - this.video_memory_base;
3266
- const ch = memory.read(i);
3267
- if (this.cache[cache_i] !== ch) {
3268
- this.draw_char(x, y, ch);
3269
- this.cache[cache_i] = ch;
3270
- }
3271
- i += 1;
3272
- }
3273
- }
3274
- setTimeout(() => this.draw_screen(), Screen.#update_rate);
3275
- }
3276
- handle_mousemove(event) {
3277
- const canvas = this.machine.ui.canvas;
3278
- const box = canvas.getBoundingClientRect();
3279
- const scaleX = canvas.width / box.width;
3280
- const scaleY = canvas.height / box.height;
3281
- const mouseX = (event.clientX - box.left) * scaleX;
3282
- const mouseY = (event.clientY - box.top) * scaleY;
3283
- const x = Math.floor(mouseX / (this.char_width * this.scale_x));
3284
- const y = Math.floor(mouseY / ((this.char_height + this.char_height_gap) * this.scale_y));
3285
- this.light_pen_x = x;
3286
- this.light_pen_y = y;
3205
+ flip_cursor() {
3206
+ this.cursor_state = !this.cursor_state;
3207
+ setTimeout(() => this.flip_cursor(), this.cursor_rate);
3287
3208
  }
3288
3209
  }
3289
3210
 
@@ -3389,8 +3310,6 @@ class Tape {
3389
3310
  }
3390
3311
 
3391
3312
  // src/lib/rk86_terminal.ts
3392
- globalThis.Image = class {
3393
- };
3394
3313
  var charMap = {
3395
3314
  0: " ",
3396
3315
  1: "\u2598",
@@ -3556,35 +3475,25 @@ class IO {
3556
3475
  interrupt = (_iff) => {};
3557
3476
  }
3558
3477
 
3559
- class TerminalScreen {
3478
+ class TerminalRenderer {
3560
3479
  machine;
3561
- width = 78;
3562
- height = 30;
3563
- video_memory_base = 0;
3564
- timer;
3565
- constructor(machine) {
3480
+ connect(machine) {
3566
3481
  this.machine = machine;
3567
3482
  }
3568
- start() {
3569
- this.render();
3570
- }
3571
- render() {
3483
+ update() {
3572
3484
  const { memory, screen } = this.machine;
3573
- const cursorX = screen.cursor_x;
3574
- const cursorY = screen.cursor_y;
3575
- const cursorVisible = screen.cursor_state;
3576
3485
  const dim = "\x1B[2m";
3577
3486
  const reset = "\x1B[0m";
3578
- const w = this.width;
3487
+ const w = screen.width;
3579
3488
  let output = "\x1B[H";
3580
3489
  output += `${dim}\u250C${"\u2500".repeat(w)}\u2510${reset}
3581
3490
  `;
3582
- let addr = this.video_memory_base;
3583
- for (let y = 0;y < this.height; y++) {
3491
+ let addr = screen.video_memory_base;
3492
+ for (let y = 0;y < screen.height; y++) {
3584
3493
  let line = `${dim}\u2502${reset}`;
3585
3494
  for (let x = 0;x < w; x++) {
3586
3495
  const ch = rk86char(memory.read(addr));
3587
- if (x === cursorX && y === cursorY) {
3496
+ if (x === screen.cursor_x && y === screen.cursor_y) {
3588
3497
  line += `\x1B[4m${ch}${reset}`;
3589
3498
  } else {
3590
3499
  line += ch;
@@ -3598,7 +3507,6 @@ class TerminalScreen {
3598
3507
  output += `${dim}\u2514${"\u2500".repeat(w)}\u2518${reset}
3599
3508
  `;
3600
3509
  process.stdout.write(output);
3601
- this.timer = setTimeout(() => this.render(), 40);
3602
3510
  }
3603
3511
  }
3604
3512
  var KEY_MAP = {
@@ -3701,12 +3609,12 @@ function decodeMon32() {
3701
3609
  return Array.from(new Uint8Array(Uint8Array.from(atob(MON32_B64), (c) => c.charCodeAt(0))));
3702
3610
  }
3703
3611
  async function fetchFile(name) {
3704
- try {
3705
- const data = await Bun.file(name).arrayBuffer();
3706
- return Array.from(new Uint8Array(data));
3707
- } catch {
3708
- console.error(`\u043E\u0448\u0438\u0431\u043A\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438 \u0444\u0430\u0439\u043B\u0430: ${name}`);
3612
+ if (!existsSync(name)) {
3613
+ console.error(`\u0444\u0430\u0439\u043B \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${name}`);
3614
+ process.exit(1);
3709
3615
  }
3616
+ const data = await readFile(name);
3617
+ return Array.from(data);
3710
3618
  }
3711
3619
  function printHelp() {
3712
3620
  console.log(`\u042D\u043C\u0443\u043B\u044F\u0442\u043E\u0440 \u0420\u0430\u0434\u0438\u043E-86\u0420\u041A (\u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B)
@@ -3753,7 +3661,7 @@ async function main() {
3753
3661
  const loadOnly = args.includes("-p");
3754
3662
  const monitorIdx = args.indexOf("-m");
3755
3663
  const monitorFile_ = monitorIdx >= 0 ? args[monitorIdx + 1] : undefined;
3756
- const positional = args.filter((a, i) => !a.startsWith("-") && i !== monitorIdx + 1);
3664
+ const positional = args.filter((a, i) => !a.startsWith("-") && (monitorIdx < 0 || i !== monitorIdx + 1));
3757
3665
  const programFile = positional[0];
3758
3666
  const keyboard = new Keyboard;
3759
3667
  const io = new IO;
@@ -3770,26 +3678,12 @@ async function main() {
3770
3678
  machine.tape = new Tape(machine);
3771
3679
  machine.runner = new Runner(machine);
3772
3680
  machine.memory.update_ruslat = machine.ui.update_ruslat;
3773
- let monitorContent;
3774
- if (monitorFile_) {
3775
- const content = await fetchFile(monitorFile_);
3776
- if (!content) {
3777
- console.error(`\u043C\u043E\u043D\u0438\u0442\u043E\u0440 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${monitorFile_}`);
3778
- process.exit(1);
3779
- }
3780
- monitorContent = content;
3781
- } else {
3782
- monitorContent = decodeMon32();
3783
- }
3681
+ const monitorContent = monitorFile_ ? await fetchFile(monitorFile_) : decodeMon32();
3784
3682
  const monitorFile = parse_rk86_binary(monitorFile_ || "mon32.bin", monitorContent);
3785
3683
  machine.memory.load_file(monitorFile);
3786
3684
  let entryPoint;
3787
3685
  if (programFile) {
3788
3686
  const content = await fetchFile(programFile);
3789
- if (!content) {
3790
- console.error(`\u0444\u0430\u0439\u043B \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${programFile}`);
3791
- process.exit(1);
3792
- }
3793
3687
  const file = parse_rk86_binary(programFile, content);
3794
3688
  machine.memory.load_file(file);
3795
3689
  entryPoint = file.entry;
@@ -3798,34 +3692,8 @@ async function main() {
3798
3692
  process.stdout.write("\x1B[?25l");
3799
3693
  process.stdout.write("\x1B[2J");
3800
3694
  setupKeyboard(keyboard);
3801
- const termScreen = new TerminalScreen(machine);
3802
- const origSetGeometry = machine.screen.set_geometry.bind(machine.screen);
3803
- machine.screen.set_geometry = (width, height) => {
3804
- origSetGeometry(width, height);
3805
- termScreen.width = width;
3806
- termScreen.height = height;
3807
- };
3808
- const origSetVideoMemory = machine.screen.set_video_memory.bind(machine.screen);
3809
- machine.screen.set_video_memory = (base) => {
3810
- origSetVideoMemory(base);
3811
- termScreen.video_memory_base = base;
3812
- };
3813
- const noopCtx = {
3814
- imageSmoothingEnabled: false,
3815
- fillStyle: "",
3816
- fillRect() {},
3817
- drawImage() {},
3818
- clearRect() {}
3819
- };
3820
- machine.screen.ctx = noopCtx;
3821
- machine.screen.init = () => {
3822
- machine.screen.ctx = noopCtx;
3823
- };
3824
- machine.screen.draw_screen = () => {};
3825
- machine.screen.draw_cursor = () => {};
3826
- machine.screen.start();
3695
+ machine.screen.start(new TerminalRenderer);
3827
3696
  machine.runner.execute();
3828
- termScreen.start();
3829
3697
  if (entryPoint !== undefined && !loadOnly) {
3830
3698
  setTimeout(() => machine.cpu.jump(entryPoint), 500);
3831
3699
  }