S1: the write side learns to stream
osFileStart, osFileWrite and osFileDone are the mirror of osFileInfo and osFileBlock. A program can now write a file it never holds: Pour writes twelve blocks and a tail while keeping 256 bytes of it at a time, and the host tool reads all 3,112 bytes back with every block where it was put. ONE WRITE IS OPEN AT A TIME AND COSMOS HOLDS IT. Reading needs no state - a name and an index are the whole question - but writing safely does, because the new file has to exist before the old one is thrown away and something has to remember which temporary belongs to which name. Keeping that here means the careful order is written once instead of in every program that streams. Nothing already on the disk is touched until osFileDone, so a disk without room says so while the old file is still there. That is stronger than osFileSave can manage, where the size is only known once the caller has every byte in hand. osFileSave stays: Edit and Files hand over whole documents and have no reason to learn any of this. osFileWrite refuses an index past the end of the file, and that refusal is not politeness. Files are contiguous, so block nine of a three block file is a real block belonging to something else, and writing it would put one file's bytes inside another with nothing anywhere saying so. Checked both ways: the tail block is allowed and the one past it is not. Three bugs, all of them the same shape - a register or pointer used for two things at once: DP3 carried the block count in and was popped high byte first, which is the wrong way round from every reader in the system and made the count two hundred and fifty six times too big. sbfsStreamStart took the name in DP0 and then wanted DP0 for something else before it had read it, so it walked whatever it last pointed at and reported that it could find no room. sbfsStreamWrite kept the caller's block in DP3 across a find - DP3 being the pointer a return does not put back, which is exactly why the find uses it too. What went to the disk was whatever the scan last looked at. It goes in memory now, and the file is correct because every block says which block it is; a check on the length alone would have passed all three of these. Writing no longer finds the file for each block either. Nothing moves a file once it is made, so where it starts is settled when the temporary is created. That was not even slow - a scan stops the moment it matches - but it was a walk of the directory per block for an answer that cannot change, and it is 28 per cent of the cost of writing forty blocks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
7cd5e34347
commit
9f7dffdeca
@@ -326,6 +326,7 @@ from every assembly file in it. Several are old programs written for the bare ma
|
||||
| Edit | A line editor. |
|
||||
| Stream | Reads an 84,000 byte file through a buffer of 256, which is what says a file bigger than Data Memory can be read at all. |
|
||||
| Type | Prints a named text file a block at a time, including one too large to fit in Data Memory. |
|
||||
| Pour | Writes a file a block at a time, never holding more than one block of it. Each block is filled with a byte naming itself, so a block written to the wrong place shows up as content rather than as a length. |
|
||||
| Wander | Goes to the directory it is given and reads a file there by a bare name. The only thing that moves the machine from inside a program, and so the only thing that can check the shell puts the working directory back afterwards. |
|
||||
| More | A forward-only pager. Space advances a screen, Return one line, and q stops. |
|
||||
|
||||
@@ -509,6 +510,9 @@ Those numbers are written down once, in `Programs/CosmOS/Source/services.asm`, w
|
||||
| osFileInfo | DP0 names a file. Q is zero if it is there, and DP3 comes back holding how many blocks it occupies. |
|
||||
| osFileBlock | DP0 names a file, DP1 says where to put a block of it, A and B together are which block counting from zero. Q is zero if it read, and DP3 comes back holding how many of the block's bytes belong to the file. |
|
||||
| osChangeDir | DP0 names a directory. Q is zero if the machine is now in it. What a program changes here, the shell puts back when the program stops. |
|
||||
| osFileStart | DP0 names a file, DP3 is how many whole blocks and A is the bytes left over in the last one. Q is zero if a write is now open. Nothing already on the disk is touched. |
|
||||
| osFileWrite | DP1 is a block, A and B together are which block of the file it is, counting from zero. Q is zero if it was written. An index past the end of the file is refused. |
|
||||
| osFileDone | No arguments. The old file goes and what was written takes its name. Q is zero if it was committed. |
|
||||
| osPrintNumber | A and B together are a number. Prints it in decimal, without leading zeroes. |
|
||||
| osBreak | Stops the program, shows every register as it had them, waits for a key, and carries on. |
|
||||
|
||||
@@ -641,6 +645,46 @@ Finding a file and listing what is there are different jobs. sbfsFind searches f
|
||||
|
||||
A file's size is settled when it is made, because nothing can grow one afterwards. Files are laid down contiguously, so the block after a file usually belongs to somebody else. A program that does not know how much it will write has to guess high and accept the slack, or build its output elsewhere and make the file once the size is known.
|
||||
|
||||
### Writing A File Too Big To Hold:
|
||||
|
||||
`osFileSave` is handed a whole document at once, which is what a text editor has. A program
|
||||
that produces its output a piece at a time - an assembler, say - would have to hold all of
|
||||
it first, and the largest thing on this machine would then be limited by memory rather than
|
||||
by the disk.
|
||||
|
||||
So there is the other half of the streaming pair. `osFileInfo` and `osFileBlock` read a
|
||||
file a block at a time; `osFileStart`, `osFileWrite` and `osFileDone` write one.
|
||||
|
||||
```text
|
||||
SETD.0 Name
|
||||
SETD.3 0x00 0x06 ; six whole blocks
|
||||
INIA 0d40 ; and forty bytes after them
|
||||
SWI osFileStart
|
||||
|
||||
...for each block: DP1 the bytes, A and B which block...
|
||||
SWI osFileWrite
|
||||
|
||||
SWI osFileDone
|
||||
```
|
||||
|
||||
**One write is open at a time, and the system holds it rather than the program.** Reading
|
||||
needs no state - a name and an index are the whole question - but writing safely does,
|
||||
because the new file has to exist before the old one is thrown away and something has to
|
||||
remember which temporary belongs to which name. Keeping that here means the careful order
|
||||
is written once instead of in every program that streams.
|
||||
|
||||
**Nothing already on the disk is touched until `osFileDone`.** The room for the whole file
|
||||
is taken at the start, so a disk that cannot hold it says so while the old one is still
|
||||
there. That is stronger than `osFileSave` can manage, where the size is only known once the
|
||||
caller already has every byte in hand.
|
||||
|
||||
Two limits differ between the two. `osFileSave` is handed a byte count in two registers and
|
||||
so cannot write more than 65,535 bytes; `osFileStart` is told blocks and a tail, the way an
|
||||
entry holds a size, and reaches the whole disk. And `osFileWrite` refuses an index past the
|
||||
end of the file - files are contiguous, so block nine of a three block file is a real block
|
||||
belonging to something else, and writing it would put one file's bytes inside another with
|
||||
nothing anywhere saying so.
|
||||
|
||||
### Saving Something Twice:
|
||||
|
||||
Which is why saving a document is not the same as writing a file, and why sbfsSaveFile exists rather than each tool doing it. A file that has grown will usually not fit where it was, so saving it means putting it somewhere else and letting go of where it was - and **the obvious order is a trap**:
|
||||
|
||||
Reference in New Issue
Block a user