Lazily load sprite image data
This cuts memory use significantly, since many sprites in an object are never used. We can get savings over time by evicting sprites when they go out of scope, but that's, well, out of scope. To achieve this, I introduce an assetstore package that is in charge of loading things from the filesystem. This also allows some lingering case-sensitivity issues to be handled cleanly. I'd hoped that creating fewer ebiten.Image instances would help CPU usage, but that doesn't seem to be the case.
This commit is contained in:
@@ -3,6 +3,7 @@ package data
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"image"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -40,6 +41,16 @@ type Sprite struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (s *Sprite) ToImage() *image.Paletted {
|
||||
return &image.Paletted{
|
||||
Pix: s.Data,
|
||||
Stride: int(s.Width),
|
||||
Rect: image.Rect(0, 0, int(s.Width), int(s.Height)),
|
||||
Palette: ColorPalette,
|
||||
}
|
||||
}
|
||||
|
||||
// dirEntry totals 8 bytes on disk
|
||||
type dirEntry struct {
|
||||
Offset uint32 // Offset of the sprite relative to the data block
|
||||
Size uint32 // Size of the sprite in bytes, including any header
|
||||
@@ -84,7 +95,7 @@ type Object struct {
|
||||
Sprites []*Sprite
|
||||
}
|
||||
|
||||
func LoadObject(filename string) (*Object, error) {
|
||||
func LoadObjectLazily(filename string) (*Object, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -101,53 +112,85 @@ func LoadObject(filename string) (*Object, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now load all sprites into memory
|
||||
dir := make([]dirEntry, out.NumSprites)
|
||||
if _, err := f.Seek(int64(out.DirOffset), io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("Seeking to sprite directory: %v", err)
|
||||
}
|
||||
|
||||
if err := binary.Read(f, binary.LittleEndian, &dir); err != nil {
|
||||
return nil, fmt.Errorf("Reading sprite directory: %v", err)
|
||||
}
|
||||
|
||||
if _, err := f.Seek(int64(out.DataOffset), io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("Seeking to sprites: %v", err)
|
||||
}
|
||||
|
||||
for i, dirEntry := range dir {
|
||||
if err := dirEntry.Check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := f.Seek(int64(out.DataOffset+dirEntry.Offset), io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("Seeking to sprite %v: %v", i, err)
|
||||
}
|
||||
|
||||
sprite := &Sprite{}
|
||||
|
||||
if err := binary.Read(f, binary.LittleEndian, &sprite.SpriteHeader); err != nil {
|
||||
return nil, fmt.Errorf("Reading sprite %v header: %v", i, err)
|
||||
}
|
||||
|
||||
if err := sprite.Check(dirEntry.Size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := io.LimitReader(f, int64(sprite.PixelSize))
|
||||
sprite.Data = make([]byte, int(sprite.Height)*int(sprite.Width))
|
||||
|
||||
// The pixel data is RLE-compressed. Uncompress it here.
|
||||
if err := rle.Expand(buf, sprite.Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.Sprites = append(out.Sprites, sprite)
|
||||
}
|
||||
out.Sprites = make([]*Sprite, int(out.NumSprites))
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func LoadObject(filename string) (*Object, error) {
|
||||
obj, err := LoadObjectLazily(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := obj.LoadAllSprites(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (o *Object) LoadAllSprites() error {
|
||||
for i := 0; i < int(o.NumSprites); i++ {
|
||||
if err := o.LoadSprite(i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Object) LoadSprite(idx int) error {
|
||||
if idx < 0 || idx >= int(o.NumSprites) {
|
||||
return fmt.Errorf("Asked for idx %v of %v", idx, o.NumSprites)
|
||||
}
|
||||
|
||||
f, err := os.Open(o.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var entry dirEntry
|
||||
|
||||
if _, err := f.Seek(int64(o.DirOffset)+int64(idx*8), io.SeekStart); err != nil {
|
||||
return fmt.Errorf("Seeking to sprite directory entry %v: %v", idx, err)
|
||||
}
|
||||
|
||||
if err := binary.Read(f, binary.LittleEndian, &entry); err != nil {
|
||||
return fmt.Errorf("Reading sprite directory entry %v: %v", idx, err)
|
||||
}
|
||||
|
||||
if err := entry.Check(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := f.Seek(int64(o.DataOffset+entry.Offset), io.SeekStart); err != nil {
|
||||
return fmt.Errorf("Seeking to sprite %v: %v", idx, err)
|
||||
}
|
||||
|
||||
sprite := &Sprite{}
|
||||
|
||||
if err := binary.Read(f, binary.LittleEndian, &sprite.SpriteHeader); err != nil {
|
||||
return fmt.Errorf("Reading sprite %v header: %v", idx, err)
|
||||
}
|
||||
|
||||
if err := sprite.Check(entry.Size); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := io.LimitReader(f, int64(sprite.PixelSize))
|
||||
sprite.Data = make([]byte, int(sprite.Height)*int(sprite.Width))
|
||||
|
||||
// The pixel data is RLE-compressed. Uncompress it here.
|
||||
if err := rle.Expand(buf, sprite.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Sprites[idx] = sprite
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadObjects(dir string) (map[string]*Object, error) {
|
||||
fis, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
|
Reference in New Issue
Block a user