Squash the Drive type hierarchy

Before, the type of the drive depended on the type of the disk in the
drive. Thus, `NibbleDrive` contained a `NibbleDisk` and a `WozDrive`
contained a `WozDisk`.  With the extraction of the disk data to a
single field, this type hierarchy makes no sense.  Instead, it
suffices to check the type of the disk.

This change removes the `NibbleDrive` and `WozDrive` types and type
guards, checking the disk type where necessary. This change also
introduces the `NoFloppyDisk` type to represent the lack of a
disk. This allows the drive to have metadata, for one.

All tests pass, everything compiles, and both WOZ and nibble disks
work locally.
This commit is contained in:
Ian Flanigan 2022-09-09 18:36:34 +02:00
parent 62b48d3893
commit 99ad84a5ec
No known key found for this signature in database
GPG Key ID: 035F657DAE4AE7EC
2 changed files with 46 additions and 67 deletions

View File

@ -14,11 +14,9 @@ import {
DRIVE_NUMBERS, DRIVE_NUMBERS,
DriveNumber, DriveNumber,
JSONDisk, JSONDisk,
ENCODING_NIBBLE,
PROCESS_BINARY, PROCESS_BINARY,
PROCESS_JSON_DISK, PROCESS_JSON_DISK,
PROCESS_JSON, PROCESS_JSON,
ENCODING_BITSTREAM,
MassStorage, MassStorage,
MassStorageData, MassStorageData,
DiskMetadata, DiskMetadata,
@ -29,6 +27,9 @@ import {
NibbleDisk, NibbleDisk,
isNibbleDisk, isNibbleDisk,
isWozDisk, isWozDisk,
NoFloppyDisk,
isNoFloppyDisk,
NO_DISK,
} from '../formats/types'; } from '../formats/types';
import { import {
@ -216,7 +217,7 @@ export interface Callbacks {
} }
/** Common information for Nibble and WOZ disks. */ /** Common information for Nibble and WOZ disks. */
interface BaseDrive { interface Drive {
/** The disk in the drive. */ /** The disk in the drive. */
disk: FloppyDisk; disk: FloppyDisk;
/** Metadata about the disk image */ /** Metadata about the disk image */
@ -233,26 +234,6 @@ interface BaseDrive {
dirty: boolean; dirty: boolean;
} }
/** WOZ format track data from https://applesaucefdc.com/woz/reference2/. */
interface WozDrive extends BaseDrive {
disk: WozDisk;
}
/** Nibble format track data. */
interface NibbleDrive extends BaseDrive {
disk: NibbleDisk;
}
type Drive = WozDrive | NibbleDrive;
function isNibbleDrive(drive: Drive): drive is NibbleDrive {
return drive.disk.encoding === ENCODING_NIBBLE;
}
function isWozDrive(drive: Drive): drive is WozDrive {
return drive.disk.encoding === ENCODING_BITSTREAM;
}
interface DriveState { interface DriveState {
disk: FloppyDisk; disk: FloppyDisk;
metadata: DiskMetadata; metadata: DiskMetadata;
@ -283,10 +264,18 @@ function getDriveState(drive: Drive): DriveState {
}; };
} }
function getDiskState(disk: NoFloppyDisk): NoFloppyDisk;
function getDiskState(disk: NibbleDisk): NibbleDisk; function getDiskState(disk: NibbleDisk): NibbleDisk;
function getDiskState(disk: WozDisk): WozDisk; function getDiskState(disk: WozDisk): WozDisk;
function getDiskState(disk: FloppyDisk): FloppyDisk; function getDiskState(disk: FloppyDisk): FloppyDisk;
function getDiskState(disk: FloppyDisk): FloppyDisk { function getDiskState(disk: FloppyDisk): FloppyDisk {
if (isNoFloppyDisk(disk)) {
return {
encoding: disk.encoding,
metadata: {...disk.metadata},
readOnly: disk.readOnly,
};
}
if (isNibbleDisk(disk)) { if (isNibbleDisk(disk)) {
const result: NibbleDisk = { const result: NibbleDisk = {
format: disk.format, format: disk.format,
@ -323,30 +312,16 @@ function getDiskState(disk: FloppyDisk): FloppyDisk {
} }
function setDriveState(state: DriveState) { function setDriveState(state: DriveState) {
if (isNibbleDisk(state.disk)) { const result: Drive = {
const result: NibbleDrive = { disk: getDiskState(state.disk),
disk: getDiskState(state.disk), track: state.track,
track: state.track, head: state.head,
head: state.head, phase: state.phase,
phase: state.phase, readOnly: state.readOnly,
readOnly: state.readOnly, dirty: state.dirty,
dirty: state.dirty, metadata: { ...state.metadata },
metadata: { ...state.metadata }, };
}; return result;
return result;
} else if (isWozDisk(state.disk)) {
const result: WozDrive = {
disk: getDiskState(state.disk),
track: state.track,
head: state.head,
phase: state.phase,
readOnly: state.readOnly,
dirty: state.dirty,
metadata: { ...state.metadata },
};
return result;
}
throw new Error('Unable to restore drive state');
} }
/** /**
@ -357,10 +332,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
private drives: Record<DriveNumber, Drive> = { private drives: Record<DriveNumber, Drive> = {
1: { // Drive 1 1: { // Drive 1
disk: { disk: {
format: 'dsk', encoding: NO_DISK,
encoding: ENCODING_NIBBLE,
volume: 254,
tracks: [],
readOnly: true, readOnly: true,
metadata: { name: 'Disk 1' }, metadata: { name: 'Disk 1' },
}, },
@ -373,10 +345,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
}, },
2: { // Drive 2 2: { // Drive 2
disk: { disk: {
format: 'dsk', encoding: NO_DISK,
encoding: ENCODING_NIBBLE,
volume: 254,
tracks: [],
readOnly: true, readOnly: true,
metadata: { name: 'Disk 2' }, metadata: { name: 'Disk 2' },
}, },
@ -493,7 +462,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
let workCycles = (cycles - this.lastCycles) * 2; let workCycles = (cycles - this.lastCycles) * 2;
this.lastCycles = cycles; this.lastCycles = cycles;
if (!isWozDrive(this.cur)) { if (!isWozDisk(this.cur.disk)) {
return; return;
} }
const track = const track =
@ -574,7 +543,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
// Only called for non-WOZ disks // Only called for non-WOZ disks
private readWriteNext() { private readWriteNext() {
if (!isNibbleDrive(this.cur)) { if (!isNibbleDisk(this.cur.disk)) {
return; return;
} }
const state = this.state; const state = this.state;
@ -633,9 +602,11 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
// in the image, but real Disk II drives can seek past track 34 by // in the image, but real Disk II drives can seek past track 34 by
// at least a half track, usually a full track. Some 3rd party // at least a half track, usually a full track. Some 3rd party
// drives can seek to track 39. // drives can seek to track 39.
const maxTrack = isNibbleDrive(this.cur) const maxTrack = isNibbleDisk(this.cur.disk)
? this.cur.disk.tracks.length * 4 - 1 ? this.cur.disk.tracks.length * 4 - 1
: this.cur.disk.trackMap.length - 1; : (isWozDisk(this.cur.disk)
? this.cur.disk.trackMap.length - 1
: 0);
if (this.cur.track > maxTrack) { if (this.cur.track > maxTrack) {
this.cur.track = maxTrack; this.cur.track = maxTrack;
} }
@ -733,7 +704,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
if (state.q7) { if (state.q7) {
this.debug('clearing _q6/SHIFT'); this.debug('clearing _q6/SHIFT');
} }
if (isNibbleDrive(this.cur)) { if (isNibbleDisk(this.cur.disk)) {
this.readWriteNext(); this.readWriteNext();
} }
break; break;
@ -743,7 +714,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
if (state.q7) { if (state.q7) {
this.debug('setting _q6/LOAD'); this.debug('setting _q6/LOAD');
} }
if (isNibbleDrive(this.cur)) { if (isNibbleDisk(this.cur.disk)) {
if (readMode && !state.q7) { if (readMode && !state.q7) {
if (this.cur.readOnly) { if (this.cur.readOnly) {
state.latch = 0xff; state.latch = 0xff;
@ -855,7 +826,6 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
getMetadata(driveNo: DriveNumber) { getMetadata(driveNo: DriveNumber) {
const drive = this.drives[driveNo]; const drive = this.drives[driveNo];
return { return {
format: drive.disk.format,
track: drive.track, track: drive.track,
head: drive.head, head: drive.head,
phase: drive.phase, phase: drive.phase,
@ -867,7 +837,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
// TODO(flan): Does not work on WOZ disks // TODO(flan): Does not work on WOZ disks
rwts(disk: DriveNumber, track: byte, sector: byte) { rwts(disk: DriveNumber, track: byte, sector: byte) {
const cur = this.drives[disk]; const cur = this.drives[disk];
if (!isNibbleDrive(cur)) { if (!isNibbleDisk(cur.disk)) {
throw new Error('Can\'t read WOZ disks'); throw new Error('Can\'t read WOZ disks');
} }
return readSector(cur.disk, track, sector); return readSector(cur.disk, track, sector);
@ -898,7 +868,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
getJSON(drive: DriveNumber, pretty: boolean = false) { getJSON(drive: DriveNumber, pretty: boolean = false) {
const cur = this.drives[drive]; const cur = this.drives[drive];
if (!isNibbleDrive(cur)) { if (!isNibbleDisk(cur.disk)) {
throw new Error('Can\'t save WOZ disks to JSON'); throw new Error('Can\'t save WOZ disks to JSON');
} }
return jsonEncode(cur.disk, pretty); return jsonEncode(cur.disk, pretty);
@ -991,7 +961,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
// TODO(flan): Does not work with WOZ or D13 disks // TODO(flan): Does not work with WOZ or D13 disks
getBinary(drive: DriveNumber, ext?: Exclude<NibbleFormat, 'woz' | 'd13'>): MassStorageData | null { getBinary(drive: DriveNumber, ext?: Exclude<NibbleFormat, 'woz' | 'd13'>): MassStorageData | null {
const cur = this.drives[drive]; const cur = this.drives[drive];
if (!isNibbleDrive(cur)) { if (!isNibbleDisk(cur.disk)) {
return null; return null;
} }
const { format, readOnly, tracks, volume } = cur.disk; const { format, readOnly, tracks, volume } = cur.disk;
@ -1028,7 +998,7 @@ export default class DiskII implements Card<State>, MassStorage<NibbleFormat> {
// TODO(flan): Does not work with WOZ or D13 disks // TODO(flan): Does not work with WOZ or D13 disks
getBase64(drive: DriveNumber) { getBase64(drive: DriveNumber) {
const cur = this.drives[drive]; const cur = this.drives[drive];
if (!isNibbleDrive(cur)) { if (!isNibbleDisk(cur.disk)) {
return null; return null;
} }
const data: string[][] | string[] = []; const data: string[][] | string[] = [];

View File

@ -64,12 +64,17 @@ export interface Disk {
readOnly: boolean; readOnly: boolean;
} }
export const NO_DISK = 'empty';
export const ENCODING_NIBBLE = 'nibble'; export const ENCODING_NIBBLE = 'nibble';
export const ENCODING_BITSTREAM = 'bitstream'; export const ENCODING_BITSTREAM = 'bitstream';
export const ENCODING_BLOCK = 'block'; export const ENCODING_BLOCK = 'block';
export interface FloppyDisk extends Disk { export interface FloppyDisk extends Disk {
encoding: typeof ENCODING_NIBBLE | typeof ENCODING_BITSTREAM; encoding: typeof ENCODING_NIBBLE | typeof ENCODING_BITSTREAM | typeof NO_DISK;
}
export interface NoFloppyDisk extends FloppyDisk {
encoding: typeof NO_DISK;
} }
export interface NibbleDisk extends FloppyDisk { export interface NibbleDisk extends FloppyDisk {
@ -153,6 +158,10 @@ export function isBlockDiskFormat(f: DiskFormat): f is BlockFormat {
return f in BLOCK_FORMATS; return f in BLOCK_FORMATS;
} }
export function isNoFloppyDisk(disk: Disk): disk is NoFloppyDisk {
return (disk as NoFloppyDisk)?.encoding === NO_DISK;
}
/** Type guard for NibbleDisks */ /** Type guard for NibbleDisks */
export function isNibbleDisk(disk: Disk): disk is NibbleDisk { export function isNibbleDisk(disk: Disk): disk is NibbleDisk {
return (disk as NibbleDisk)?.encoding === ENCODING_NIBBLE; return (disk as NibbleDisk)?.encoding === ENCODING_NIBBLE;