Gopher2600/crunched/quick.go
JetSetIlly dc084bea7b added crunched package and implemented a simple RLE crunching interface
elf SRAM is crunched with this new package on Snapshot()

.gitigore updated with */testdata

added fuzz target to Makefile
2024-07-06 21:52:28 +01:00

156 lines
4.2 KiB
Go

// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package crunched
type quick struct {
crunched bool
data []byte
uncrunchedSize int
}
// NewQuick returns an implementation of the Data interface that is intended to
// perform quickly on both crunching and decrunching.
//
// For simplicity, the minimum amount of data allocated will be 4 bytes.
func NewQuick(size int) Data {
size = max(size, 4)
return &quick{
data: make([]byte, size),
uncrunchedSize: size,
}
}
// IsCrunched returns true if data is currently crunched
//
// This function implements the Data interface
func (c *quick) IsCrunched() bool {
return c.crunched
}
// Size returns the uncrunched size and the current size of the data. If the
// data is currently crunched then the two values will be the same
//
// This function implements the Data interface
func (c *quick) Size() (int, int) {
return c.uncrunchedSize, len(c.data)
}
// Data returns a pointer to the uncrunched data
//
// This function implements the Data interface
func (c *quick) Data() *[]byte {
if c.crunched {
// sanity check. with the current RLE method the number of bytes in the
// crunched data should be a multiple of two
if len(c.data)&0x01 == 0x01 {
panic("crunched data should have an even number of bytes")
}
// make a reference to the crunched data before creating space for the
// uncrunched data
working := c.data
c.data = make([]byte, c.uncrunchedSize)
// undo the RLE process
var idx int
for i := 0; i < len(working); i += 2 {
for r := 0; r <= int(working[i+1]); r++ {
c.data[idx] = working[i]
idx++
}
}
// data is now uncrunched
c.crunched = false
}
return &c.data
}
// Snapshot makes a copy of the data and crunching it if required. The data will
// be uncrunched automatically when Data() function is called
//
// This function implements the Data interface
func (c *quick) Snapshot() Data {
d := *c
if !d.crunched {
working := make([]byte, d.uncrunchedSize)
var ct int
var idx int
working[idx] = c.data[0]
// assume crunching has succeeded unless explicitely told otherwise
d.crunched = true
// very basic RLE algorithm:
// 1) each byte is followed by a count value
// 2) maximum count value is 255
for _, v := range c.data[1:] {
if v == working[idx] && ct < 255 {
ct++
} else {
// check that the crunched data isn't getting too large. we'll
// be adding two bytes to the crunch stream so the check here is
// to make sure that won't overflow the size of the array
if idx >= len(working)-2 {
d.crunched = false
break // for loop
}
// output count to the crunch stream
idx++
working[idx] = byte(ct)
// output new byte to crunch stream
idx++
working[idx] = v
// count will begin again with the new byte
ct = 0
}
}
// if the data has been crunched then allocate just enough memory to
// store the crunched data before returning
if d.crunched {
idx++
working[idx] = byte(ct)
d.data = make([]byte, idx+1)
copy(d.data, working[:idx+1])
return &d
}
// if data is not crunched then we intentionally fall through to the
// plain data copy below
}
// copy data as it exists now. this may be crunched data or uncrunched data.
// it doesn't matter either way
d.data = make([]byte, len(c.data))
copy(d.data, c.data)
return &d
}
// Inspect returns data in the current state. In other words, the data will
// not be decrunched as it would be with the Data() function
//
// This function implements the Peep interface
func (c *quick) Inspect() *[]byte {
return &c.data
}