Ok, so I've written up a banked backed file cache implementation in C.
I've got it here.
https://www.dropbox.com/s/zp3dfynu3cq9bym/Cache64x.zip?dl=0
Now, that's an XCode project, but the C code is there. There's no Makefile. It should be straightforward to incorporate into anything, it's just 2 files: cache64.c and cache64.h
Obviously, I have not compiled this with CC65. I do hope that CC65 has code for 32 bit values. I assume it does, but I don't know.
I use uint8_t (unsigned byte), uint16_t (unsigned 16b int), uint32_t (unsigned 32b int) types, not exclusively, but where I feel it's important to be specific on the data sizes.
The architecture is pretty simple, and cheap. For 64 banks, the overhead is 4 bytes per bank, plus 5 for house keeping, 261 bytes.
You tie the cache to a file, and you can allocated as many banks to that file as you like. Each cache has a starting bank, and a bank count. So you could work with several files if you like, or just one big one.
First, we have the concept of a block. A block is a chunk of the master file that's the size of the bank. Each block has an id, which is simply the offset within the file divided by the block size. This lets us use 16 bit block ids. With an 8K block/bank size, and 65K different block ids, that's 512MB. "Should be big enough for you, old man. What's the cargo?"
We have a list of blocks, sorted by block_id, which tells us which blocks of the file are in the cache, and which bank they're associate with.
Then, we have an LRU (Least Recently Used) list. The top of that list is the most recently used bank, the bottom is the least.
The read routine crassly assumes that the amount of data to read is LESS THAN a block size, but it DOES span blocks, so there's no concern with alignment. It could readily be change to read data up to 16 bits in length. I just don't.
You read data, just like a normal read.
Code:
uint16_t cache64_read(cache64 *cache, uint32_t offset, uint16_t size, void *buffer);
So, if you had a structure, say star_system, and you wanted to read the 100th star_system from the file, you could do something like:
Code:
start_system my_system;
uint16_t size = cache64_read(cache, sizeof(star_system) * 100, sizeof(star_system), &my_system);
The size returns is just the size you pass in. It's not really set up to return partial results, it pretty much assumes you know where the data is, how much there is, etc. Reading past the EOF is kind of "undefined". So, "don't do that".
Given the offset to read, we discern the block_id, look it up in the block list, and if it's not there, we check if the list is full. If not, we'll just insert it into the list.
Whenever we use a block, we grab it's bank and move it to the top of the LRU list.
If the list is full, then we take the bank at the bottom of the LRU list, search block list for it, and remove the block. Right now, when we remove a block from the list, nothing happens. But if you wanted to make the cache write-through, you could do this here by dirtying the blocks and flushing them back to disk when they're about to be removed from the list.
The cache structure relies on dynamic memory, but could easily be made static.
Also, the cache structure has a "banks" array which is used to simulate the "banking" system. Obviously, the needs to be removed for the real system.
There is no error handling.
It currently relies on stdio routines: fopen, fread, fseek. Those will have to be replaced by the x16/CC65 equivalents, but should be straightforward. It assumed, again, the ability to use a 32bit offset in to an arbitrary point in the SD file, and that CC65 can handle that.
I haven't written C in decades, but this seems ok to me.