improved buffers library, added to docs

This commit is contained in:
Irmen de Jong 2024-11-27 00:21:33 +01:00
parent bc9683cc54
commit 5406a992f5
5 changed files with 501 additions and 125 deletions

View File

@ -1,69 +1,130 @@
; **experimental** buffer data structures, API subject to change!! ; **experimental** buffer data structures, API subject to change!!
%option no_symbol_prefixing, ignore_unused %option no_symbol_prefixing, ignore_unused
smallringbuffer {
; -- A ringbuffer (FIFO queue) that occupies a single page in memory, containing 255 bytes maximum.
; You can store and retrieve bytes and words too.
; It's optimized for speed and depends on the byte-wrap-around feature when doing incs and decs.
ubyte fill smallringbuffer {
ubyte head ; -- A ringbuffer (FIFO queue) that occupies 256 bytes in memory.
ubyte tail ; You can store and retrieve bytes and words. No guards against over/underflow.
; It's optimized for speed and depends on the byte-wrap-around when doing incs and decs.
ubyte fill = 0
ubyte head = 0
ubyte tail = 255
ubyte[256] buffer ubyte[256] buffer
sub init() { sub init() {
; -- (re)initialize the ringbuffer, you must call this before using the other routines ; -- (re)initialize the ringbuffer
head = fill = 0 head = fill = 0
tail = 255 tail = 255
} }
sub put(ubyte value) -> bool { sub size() -> ubyte {
; -- store a byte in the buffer, returns success return fill
if fill==255 }
return false
sub free() -> ubyte {
return 255-fill
}
sub isfull() -> bool {
return fill>=254
}
sub isempty() -> bool {
return fill<=1
}
sub put(ubyte value) {
; -- store a byte in the buffer
buffer[head] = value buffer[head] = value
head++ head++
fill++ fill++
return true
} }
sub putw(uword value) -> bool { sub putw(uword value) {
; -- store a word in the buffer, returns success ; -- store a word in the buffer
if fill>=254
return false
fill += 2 fill += 2
buffer[head] = lsb(value) buffer[head] = lsb(value)
head++ head++
buffer[head] = msb(value) buffer[head] = msb(value)
head++ head++
return true
} }
sub get() -> ubyte { sub get() -> ubyte {
; -- retrieves a byte from the buffer. Also sets Carry flag: set=success, clear=buffer was empty ; -- retrieves a byte from the buffer
if fill==0 {
sys.clear_carry()
return 0
}
fill-- fill--
tail++ tail++
sys.set_carry()
return buffer[tail] return buffer[tail]
} }
sub getw() -> uword { sub getw() -> uword {
; -- retrieves a word from the buffer. Also sets Carry flag: set=success, clear=buffer was empty ; -- retrieves a word from the buffer
if fill<2 {
sys.clear_carry()
return 0
}
fill -= 2 fill -= 2
tail++ tail++
cx16.r0L = buffer[tail] cx16.r0L = buffer[tail]
tail++ tail++
cx16.r0H = buffer[tail] cx16.r0H = buffer[tail]
sys.set_carry() return cx16.r0
}
}
; note: for a "small stack" (256 bytes size) just use the CPU stack via sys.push[w] / sys.pop[w].
stack {
; -- A stack (LIFO) that uses a block of 8 KB of memory. Growing downward from the top of the buffer.
; You can store and retrieve bytes and words. There are no guards against stack over/underflow.
uword sp = 8191
uword buffer_ptr = memory("buffers_stack", 8192, 0)
sub init() {
sp = 8191
}
sub size() -> uword {
return 8191-sp
}
sub free() -> uword {
return sp
}
sub isfull() -> bool {
return sp==0
}
sub isempty() -> bool {
return sp==8191
}
sub push(ubyte value) {
; -- put a byte on the stack
buffer_ptr[sp] = value
sp--
}
sub pushw(uword value) {
; -- put a word on the stack (lsb first then msb)
buffer_ptr[sp] = lsb(value)
sp--
buffer_ptr[sp] = msb(value)
sp--
}
sub pop() -> ubyte {
; -- pops a byte off the stack
sp++
return buffer_ptr[sp]
}
sub popw() -> uword {
; -- pops a word off the stack.
sp++
cx16.r0H = buffer_ptr[sp]
sp++
cx16.r0L = buffer_ptr[sp]
return cx16.r0 return cx16.r0
} }
} }
@ -71,66 +132,64 @@ smallringbuffer {
ringbuffer { ringbuffer {
; -- A ringbuffer (FIFO queue) that uses a block of 8 KB of memory. ; -- A ringbuffer (FIFO queue) that uses a block of 8 KB of memory.
; You can store and retrieve bytes and words too. ; You can store and retrieve bytes and words too. No guards against buffer under/overflow.
uword fill uword fill = 0
uword head uword head = 0
uword tail uword tail = 8191
uword buffer_ptr = memory("ringbuffer", 8192, 0) uword buffer_ptr = memory("buffers_ringbuffer", 8192, 0)
sub init() { sub init() {
; -- (re)initialize the ringbuffer, you must call this before using the other routines
head = fill = 0 head = fill = 0
tail = 8191 tail = 8191
} }
sub put(ubyte value) -> bool { sub size() -> uword {
; -- store a byte in the buffer, returns success return fill
if fill==8192 }
return false
sub free() -> uword {
return 8191-fill
}
sub isempty() -> bool {
return fill==0
}
sub isfull() -> bool {
return fill>=8191
}
sub put(ubyte value) {
; -- store a byte in the buffer
buffer_ptr[head] = value buffer_ptr[head] = value
inc_head() inc_head()
fill++ fill++
return true
} }
sub putw(uword value) -> bool { sub putw(uword value) {
; -- store a word in the buffer, returns success ; -- store a word in the buffer
if fill>=8191
return false
fill += 2 fill += 2
buffer_ptr[head] = lsb(value) buffer_ptr[head] = lsb(value)
inc_head() inc_head()
buffer_ptr[head] = msb(value) buffer_ptr[head] = msb(value)
inc_head() inc_head()
return true
} }
sub get() -> ubyte { sub get() -> ubyte {
; -- retrieves a byte from the buffer. Also sets Carry flag: set=success, clear=buffer was empty ; -- retrieves a byte from the buffer
if fill==0 {
sys.clear_carry()
return 0
}
fill-- fill--
inc_tail() inc_tail()
cx16.r0L = buffer_ptr[tail] return buffer_ptr[tail]
sys.set_carry()
return cx16.r0L
} }
sub getw() -> uword { sub getw() -> uword {
; -- retrieves a word from the buffer. Also sets Carry flag: set=success, clear=buffer was empty ; -- retrieves a word from the buffer
if fill<2 {
sys.clear_carry()
return 0
}
fill -= 2 fill -= 2
inc_tail() inc_tail()
cx16.r0L = buffer_ptr[tail] cx16.r0L = buffer_ptr[tail]
inc_tail() inc_tail()
cx16.r0H = buffer_ptr[tail] cx16.r0H = buffer_ptr[tail]
sys.set_carry()
return cx16.r0 return cx16.r0
} }
@ -147,6 +206,3 @@ ringbuffer {
} }
} }
; TODO ringbuffer (FIFO queue) should use banked ram on the X16, but still work on virtual target
; TODO stack (LIFO queue) using more than 1 page of ram (maybe even banked ram on the x16)

View File

@ -0,0 +1,244 @@
; **experimental** buffer data structures, API subject to change!!
%option no_symbol_prefixing, ignore_unused
smallringbuffer {
; -- A ringbuffer (FIFO queue) that occupies 256 bytes in memory.
; You can store and retrieve bytes and words. No guards against over/underflow.
; It's optimized for speed and depends on the byte-wrap-around when doing incs and decs.
ubyte fill = 0
ubyte head = 0
ubyte tail = 255
ubyte[256] buffer
sub init() {
; -- (re)initialize the ringbuffer
head = fill = 0
tail = 255
}
sub size() -> ubyte {
return fill
}
sub free() -> ubyte {
return 255-fill
}
sub isfull() -> bool {
return fill>=254
}
sub isempty() -> bool {
return fill<=1
}
sub put(ubyte value) {
; -- store a byte in the buffer
buffer[head] = value
head++
fill++
}
sub putw(uword value) {
; -- store a word in the buffer
fill += 2
buffer[head] = lsb(value)
head++
buffer[head] = msb(value)
head++
}
sub get() -> ubyte {
; -- retrieves a byte from the buffer
fill--
tail++
return buffer[tail]
}
sub getw() -> uword {
; -- retrieves a word from the buffer
fill -= 2
tail++
cx16.r0L = buffer[tail]
tail++
cx16.r0H = buffer[tail]
return cx16.r0
}
}
; note: for a "small stack" (256 bytes size) just use the CPU stack via sys.push[w] / sys.pop[w].
stack {
; -- A stack (LIFO) that uses a block of 8 KB of memory. Growing downward from the top of the buffer.
; You can store and retrieve bytes and words. There are no guards against stack over/underflow.
uword sp
ubyte bank
sub init(ubyte rambank) {
; -- initialize the stack, must be called before use. Supply the HiRAM bank to use as buffer space.
sp = $bfff
bank = rambank
}
sub size() -> uword {
return $bfff-sp
}
sub free() -> uword {
return sp-$a000
}
sub isfull() -> bool {
return sp<=$a001
}
sub isempty() -> bool {
return sp==$bfff
}
sub push(ubyte value) {
; -- put a byte on the stack
sys.push(cx16.getrambank())
cx16.rambank(bank)
@(sp) = value
sp--
cx16.rambank(sys.pop())
}
sub pushw(uword value) {
; -- put a word on the stack (lsb first then msb)
sys.push(cx16.getrambank())
cx16.rambank(bank)
@(sp) = lsb(value)
sp--
@(sp) = msb(value)
sp--
cx16.rambank(sys.pop())
}
sub pop() -> ubyte {
; -- pops a byte off the stack
sys.push(cx16.getrambank())
cx16.rambank(bank)
sp++
cx16.r0L = @(sp)
cx16.rambank(sys.pop())
return cx16.r0L
}
sub popw() -> uword {
; -- pops a word off the stack.
sys.push(cx16.getrambank())
cx16.rambank(bank)
sp++
cx16.r0H = @(sp)
sp++
cx16.r0L = @(sp)
cx16.rambank(sys.pop())
return cx16.r0
}
}
ringbuffer {
; -- A ringbuffer (FIFO queue) that uses a block of 8 KB of memory.
; You can store and retrieve bytes and words too. No guards against buffer under/overflow.
uword fill, head, tail
ubyte bank = 255 ; set via init()
sub init(ubyte rambank) {
; -- initialize the ringbuffer, must be called before use. Supply the HiRAM bank to use as buffer space.
head = $a000
tail = $bfff
fill = 0
bank = rambank
}
sub size() -> uword {
return fill
}
sub free() -> uword {
return $1fff-fill
}
sub isempty() -> bool {
return fill<=1
}
sub isfull() -> bool {
return fill>=8191
}
sub put(ubyte value) {
; -- store a byte in the buffer
sys.push(cx16.getrambank())
cx16.rambank(bank)
@(head) = value
fill++
inc_head()
cx16.rambank(sys.pop())
}
sub putw(uword value) {
; -- store a word in the buffer
sys.push(cx16.getrambank())
cx16.rambank(bank)
pokew(head, value)
fill += 2
inc_head()
inc_head()
cx16.rambank(sys.pop())
}
sub get() -> ubyte {
; -- retrieves a byte from the buffer
sys.push(cx16.getrambank())
cx16.rambank(bank)
fill--
inc_tail()
cx16.r0L= @(tail)
cx16.rambank(sys.pop())
return cx16.r0L
}
sub getw() -> uword {
; -- retrieves a word from the buffer
sys.push(cx16.getrambank())
cx16.rambank(bank)
fill -= 2
inc_tail()
cx16.r0L = @(tail)
inc_tail()
cx16.r0H = @(tail)
cx16.rambank(sys.pop())
return cx16.r0
}
sub inc_head() {
head++
if msb(head)==$c0
head=$a000
}
sub inc_tail() {
tail++
if msb(tail)==$c0
tail=$a000
}
}

View File

@ -410,6 +410,51 @@ Routines to check if any or all values in an array or memory buffer are not zero
Doesn't work on split arrays. Doesn't work on split arrays.
buffers (experimental)
----------------------
A small library providing a 8 KB stack, an 8 KB ringbuffer, and a fast 256 bytes ringbuffer.
Stack is a LIFO container, ringbuffers are FIFO containers.
On the Commander X16 the stack and ringbuffer will use a HiRAM bank instead of system ram,
you have to initialize that via the init(bank) routine.
Read the `buffers source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/diskio.p8>`_
to see what's in there. Note that the init() routines have that extra bank parameter on the cx16.
compression
-----------
Routines for data compression and decompression. Currently only the 'ByteRun1' aka 'PackBits' RLE encoding
is available. This is the compression that was also used in Amiga IFF images and in old MacPaint images.
``encode_rle (uword data, uword size, uword target, bool is_last_block) -> uword``
Compress the given data block using ByteRun1 aka PackBits RLE encoding.
Returns the size of the compressed RLE data. Worst case result storage size needed = (size + (size+126) / 127) + 1.
'is_last_block' = usually true, but you can set it to false if you want to concatenate multiple
compressed blocks (for instance if the source data is >64Kb)
``encode_rle_outfunc (uword data, uword size, uword output_function, bool is_last_block)``
Like ``encode_rle`` but not with an output buffer, but with an 'output_function' argument.
This is the address of a routine that gets a byte arg in A,
which is the next RLE byte to write to the compressed output buffer or file.
``decode_rle (uword compressed, uword target, uword maxsize) -> uword``
Decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
Also stops decompressing if the maxsize has been reached. Returns the size of the decompressed data.
``decode_rle_srcfunc (uword source_function, uword target, uword maxsize) -> uword``
Decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
Also stops decompressing when the maxsize has been reached. Returns the size of the decompressed data.
Instead of a source buffer, you provide a callback function that must return the next byte to compress in A.
``decode_rle_vram (uword compressed, ubyte vbank, uword vaddr)`` (cx16 only)
Decodes "ByteRun1" (aka PackBits) RLE compressed data directly into Vera VRAM.
Control byte value 128 ends the decoding.
While the X16 has pretty fast lzsa decompression in the kernal, RLE is still a lot faster to decode.
However it also doesn't compress data nearly as well, but that's the usual tradeoff.
There is a *compression* routine as well for RLE that you can run on the X16 itself,
something that the lzsa compression lacks.
conv conv
---- ----
Routines to convert strings to numbers or vice versa. Routines to convert strings to numbers or vice versa.
@ -928,37 +973,6 @@ the `bmx source code <https://github.com/irmen/prog8/tree/master/compiler/res/pr
There's also the "showbmx" example to look at. There's also the "showbmx" example to look at.
compression
-----------
Routines for data compression and decompression. Currently only the 'ByteRun1' aka 'PackBits' RLE encoding
is available. This is the compression that was also used in Amiga IFF images and in old MacPaint images.
``encode_rle (uword data, uword size, uword target, bool is_last_block) -> uword``
Compress the given data block using ByteRun1 aka PackBits RLE encoding.
Returns the size of the compressed RLE data. Worst case result storage size needed = (size + (size+126) / 127) + 1.
'is_last_block' = usually true, but you can set it to false if you want to concatenate multiple
compressed blocks (for instance if the source data is >64Kb)
``encode_rle_outfunc (uword data, uword size, uword output_function, bool is_last_block)``
Like ``encode_rle`` but not with an output buffer, but with an 'output_function' argument.
This is the address of a routine that gets a byte arg in A,
which is the next RLE byte to write to the compressed output buffer or file.
``decode_rle (uword compressed, uword target, uword maxsize) -> uword``
Decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
Also stops decompressing if the maxsize has been reached. Returns the size of the decompressed data.
``decode_rle_srcfunc (uword source_function, uword target, uword maxsize) -> uword``
Decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
Also stops decompressing when the maxsize has been reached. Returns the size of the decompressed data.
Instead of a source buffer, you provide a callback function that must return the next byte to compress in A.
``decode_rle_vram (uword compressed, ubyte vbank, uword vaddr)`` (cx16 only)
Decodes "ByteRun1" (aka PackBits) RLE compressed data directly into Vera VRAM.
Control byte value 128 ends the decoding.
emudbg (cx16 only) emudbg (cx16 only)
------------------- -------------------
X16Emu Emulator debug routines, for Cx16 only. X16Emu Emulator debug routines, for Cx16 only.

View File

@ -1,14 +1,13 @@
TODO TODO
==== ====
work a bit more on the buffers library
document the @R0 - @R15 register support for normal subroutine parameters (footgun!) document the @R0 - @R15 register support for normal subroutine parameters (footgun!)
make a compiler switch to disable footgun warnings make a compiler switch to disable footgun warnings
upgrade zmskit example to use zsmkit v2
what to do with bankof(): keep it? add another syntax like \`value or ^value to get the bank byte? what to do with bankof(): keep it? add another syntax like \`value or ^value to get the bank byte?
add a function like addr() or lsw() to complement bnk() in getting easy access to the lower 16 bits of a long integer?
-> added unary ^ operator as alternative to bankof() -> added unary ^ operator as alternative to bankof()
-> added unary << operator as alternative to addr() / lsb(x>>16) / lsw() -> added unary << operator as alternative to addr() / lsb(x>>16) / lsw()
-> added msw() and lsw() . note: msw() on a 24 bits constant can ALSO be used to get the bank byte because the value, while a word type, will be <=255 -> added msw() and lsw() . note: msw() on a 24 bits constant can ALSO be used to get the bank byte because the value, while a word type, will be <=255

View File

@ -1,43 +1,106 @@
%import textio %import textio
%import buffers
%option no_sysinit
%zeropage basicsafe %zeropage basicsafe
main { main {
sub start() { sub start() {
foo(42) txt.print("stack\n")
foo(42) test_stack()
foo(42) txt.print("\nringbuffer\n")
bar(9999) test_ring()
bar(9999) txt.print("\nsmallringbuffer\n")
bar(9999) test_smallring()
baz(42, 123)
baz(42, 123)
baz(42, 123)
meh(42, 9999)
meh(42, 9999)
meh(42, 9999)
} }
sub foo(ubyte arg @R0) { sub test_stack() {
txt.print_ub(arg) stack.init(2)
txt.nl() txt.print_uw(stack.size())
}
sub bar(uword arg @R0) {
txt.print_uw(arg)
txt.nl()
}
sub baz(ubyte arg1 @R0, ubyte arg2 @R1) {
txt.print_ub(arg1)
txt.spc() txt.spc()
txt.print_ub(arg2) txt.print_uw(stack.free())
txt.nl()
stack.push(1)
stack.push(2)
stack.push(3)
stack.pushw(12345)
txt.print_uw(stack.size())
txt.spc()
txt.print_uw(stack.free())
txt.nl()
txt.nl()
txt.print_uw(stack.popw())
txt.nl()
txt.print_uw(stack.pop())
txt.nl()
txt.print_uw(stack.pop())
txt.nl()
txt.print_uw(stack.pop())
txt.nl()
txt.nl()
txt.print_uw(stack.size())
txt.spc()
txt.print_uw(stack.free())
txt.nl() txt.nl()
} }
sub meh(ubyte arg1 @R0, uword arg2 @R1) { sub test_ring() {
txt.print_ub(arg1) ringbuffer.init(2)
txt.print_uw(ringbuffer.size())
txt.spc() txt.spc()
txt.print_uw(arg2) txt.print_uw(ringbuffer.free())
txt.nl()
ringbuffer.put(1)
ringbuffer.put(2)
ringbuffer.put(3)
ringbuffer.putw(12345)
txt.print_uw(ringbuffer.size())
txt.spc()
txt.print_uw(ringbuffer.free())
txt.nl()
txt.nl()
txt.print_uw(ringbuffer.get())
txt.nl()
txt.print_uw(ringbuffer.get())
txt.nl()
txt.print_uw(ringbuffer.get())
txt.nl()
txt.print_uw(ringbuffer.getw())
txt.nl()
txt.nl()
txt.print_uw(ringbuffer.size())
txt.spc()
txt.print_uw(ringbuffer.free())
txt.nl()
}
sub test_smallring() {
smallringbuffer.init()
txt.print_uw(smallringbuffer.size())
txt.spc()
txt.print_uw(smallringbuffer.free())
txt.nl()
smallringbuffer.put(1)
smallringbuffer.put(2)
smallringbuffer.put(3)
smallringbuffer.putw(12345)
txt.print_uw(smallringbuffer.size())
txt.spc()
txt.print_uw(smallringbuffer.free())
txt.nl()
txt.nl()
txt.print_uw(smallringbuffer.get())
txt.nl()
txt.print_uw(smallringbuffer.get())
txt.nl()
txt.print_uw(smallringbuffer.get())
txt.nl()
txt.print_uw(smallringbuffer.getw())
txt.nl()
txt.nl()
txt.print_uw(smallringbuffer.size())
txt.spc()
txt.print_uw(smallringbuffer.free())
txt.nl() txt.nl()
} }
} }