Gzip Compression for Levels and Doodads
* Levels and Doodad files will be written in gzip-compressed JSON format * `boolProp compress-drawings false` to disable compression and save as classic JSON format directly * The game can still read uncompressed JSON files The file size savings on some built-in assets: * Tutorial 2.level: 2.2M -> 414K (82% smaller) * warp-door-orange.doodad: 105K -> 17K (84% smaller)
This commit is contained in:
parent
3486050702
commit
8603c43c58
|
@ -41,6 +41,10 @@ var Boolprops = map[string]Boolprop{
|
|||
Get: func() bool { return usercfg.Current.HorizontalToolbars },
|
||||
Set: func(v bool) { usercfg.Current.HorizontalToolbars = v },
|
||||
},
|
||||
"compress-drawings": {
|
||||
Get: func() bool { return CompressDrawings },
|
||||
Set: func(v bool) { CompressDrawings = v },
|
||||
},
|
||||
}
|
||||
|
||||
// GetBoolProp reads the current value of a boolProp.
|
||||
|
|
|
@ -63,6 +63,9 @@ var (
|
|||
// Publishing: Doodads-embedded-within-levels.
|
||||
EmbeddedDoodadsBasePath = "assets/doodads/"
|
||||
EmbeddedWallpaperBasePath = "assets/wallpapers/"
|
||||
|
||||
// File formats: save new levels and doodads gzip compressed
|
||||
CompressDrawings = true
|
||||
)
|
||||
|
||||
// Edit Mode Values
|
||||
|
|
43
pkg/doodads/fmt_gzip.go
Normal file
43
pkg/doodads/fmt_gzip.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package doodads
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// ToGzip serializes the doodad as gzip compressed JSON.
|
||||
func (d *Doodad) ToGzip() ([]byte, error) {
|
||||
var (
|
||||
handle = bytes.NewBuffer([]byte{})
|
||||
zipper = gzip.NewWriter(handle)
|
||||
encoder = json.NewEncoder(zipper)
|
||||
)
|
||||
if err := encoder.Encode(d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := zipper.Close()
|
||||
return handle.Bytes(), err
|
||||
}
|
||||
|
||||
// FromGzip deserializes a gzip compressed doodad JSON.
|
||||
func FromGzip(data []byte) (*Doodad, error) {
|
||||
// This function works, do not touch.
|
||||
var (
|
||||
level = &Doodad{}
|
||||
buf = bytes.NewBuffer(data)
|
||||
reader *gzip.Reader
|
||||
decoder *json.Decoder
|
||||
)
|
||||
|
||||
reader, err := gzip.NewReader(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decoder = json.NewDecoder(reader)
|
||||
decoder.Decode(level)
|
||||
|
||||
return level, nil
|
||||
}
|
|
@ -204,7 +204,6 @@ func (d *Doodad) WriteFile(filename string) error {
|
|||
|
||||
// Serialize encodes a doodad to bytes and returns them, instead
|
||||
// of writing to a file.
|
||||
// WriteFile saves a doodad to disk in the user's config directory.
|
||||
func (d *Doodad) Serialize() ([]byte, error) {
|
||||
// Set the version information.
|
||||
d.Version = 1
|
||||
|
|
|
@ -5,14 +5,23 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
)
|
||||
|
||||
// ToJSON serializes the doodad as JSON.
|
||||
// ToJSON serializes the doodad as JSON (gzip supported).
|
||||
//
|
||||
// If balance.CompressLevels=true the doodad will be gzip compressed
|
||||
// and the return value is gz bytes and not the raw JSON.
|
||||
func (d *Doodad) ToJSON() ([]byte, error) {
|
||||
// Gzip compressing?
|
||||
if balance.CompressDrawings {
|
||||
return d.ToGzip()
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
encoder := json.NewEncoder(out)
|
||||
if usercfg.Current.JSONIndent {
|
||||
|
@ -22,16 +31,32 @@ func (d *Doodad) ToJSON() ([]byte, error) {
|
|||
return out.Bytes(), err
|
||||
}
|
||||
|
||||
// FromJSON loads a doodad from JSON string.
|
||||
// FromJSON loads a doodad from JSON string (gzip supported).
|
||||
func FromJSON(filename string, data []byte) (*Doodad, error) {
|
||||
var doodad = &Doodad{}
|
||||
|
||||
// Inspect the headers of the file to see how it was encoded.
|
||||
if len(data) > 0 && data[0] == '{' {
|
||||
// Looks standard JSON.
|
||||
err := json.Unmarshal(data, doodad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if len(data) > 1 && data[0] == 0x1f && data[1] == 0x8b {
|
||||
// Gzip compressed. `1F8B` is gzip magic number.
|
||||
log.Debug("Decompress doodad %s", filename)
|
||||
if gzd, err := FromGzip(data); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
doodad = gzd
|
||||
}
|
||||
}
|
||||
|
||||
// Inflate the chunk metadata to map the pixels to their palette indexes.
|
||||
doodad.Filename = filepath.Base(filename)
|
||||
doodad.Inflate()
|
||||
|
||||
return doodad, err
|
||||
return doodad, nil
|
||||
}
|
||||
|
||||
// WriteJSON writes a Doodad to JSON on disk.
|
||||
|
@ -52,22 +77,10 @@ func (d *Doodad) WriteJSON(filename string) error {
|
|||
|
||||
// LoadJSON loads a map from JSON file.
|
||||
func LoadJSON(filename string) (*Doodad, error) {
|
||||
fh, err := os.Open(filename)
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
// Decode the JSON file from disk.
|
||||
d := New(0)
|
||||
decoder := json.NewDecoder(fh)
|
||||
err = decoder.Decode(&d)
|
||||
if err != nil {
|
||||
return d, fmt.Errorf("doodad.LoadJSON: JSON decode error: %s", err)
|
||||
}
|
||||
|
||||
// Inflate the chunk metadata to map the pixels to their palette indexes.
|
||||
d.Filename = filepath.Base(filename)
|
||||
d.Inflate()
|
||||
return d, err
|
||||
return FromJSON(filename, data)
|
||||
}
|
||||
|
|
|
@ -182,9 +182,14 @@ func (s *EditorScene) ConfirmUnload(fn func()) {
|
|||
func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
|
||||
// Update debug overlay values.
|
||||
*s.debTool = s.UI.Canvas.Tool.String()
|
||||
*s.debSwatch = s.UI.Canvas.Palette.ActiveSwatch.Name
|
||||
*s.debSwatch = "???"
|
||||
*s.debWorldIndex = s.UI.Canvas.WorldIndexAt(s.UI.cursor).String()
|
||||
|
||||
// Safely...
|
||||
if s.UI.Canvas.Palette != nil && s.UI.Canvas.Palette.ActiveSwatch != nil {
|
||||
*s.debSwatch = s.UI.Canvas.Palette.ActiveSwatch.Name
|
||||
}
|
||||
|
||||
// Has the window been resized?
|
||||
if ev.WindowResized {
|
||||
w, h := d.Engine.WindowSize()
|
||||
|
|
|
@ -2,18 +2,39 @@ package level
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
)
|
||||
|
||||
// FromJSON loads a level from JSON string.
|
||||
// FromJSON loads a level from JSON string (gzip supported).
|
||||
func FromJSON(filename string, data []byte) (*Level, error) {
|
||||
var m = New()
|
||||
|
||||
// Inspect if this file is JSON or gzip compressed.
|
||||
if len(data) > 0 && data[0] == '{' {
|
||||
// Looks standard JSON.
|
||||
err := json.Unmarshal(data, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if len(data) > 1 && data[0] == 0x1f && data[1] == 0x8b {
|
||||
// Gzip compressed. `1F8B` is gzip magic number.
|
||||
log.Debug("Decompress level %s", filename)
|
||||
if gzmap, err := FromGzip(data); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
m = gzmap
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("invalid file format")
|
||||
}
|
||||
|
||||
// Fill in defaults.
|
||||
if m.Wallpaper == "" {
|
||||
|
@ -27,11 +48,21 @@ func FromJSON(filename string, data []byte) (*Level, error) {
|
|||
// Inflate the private instance values.
|
||||
m.Palette.Inflate()
|
||||
|
||||
return m, err
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ToJSON serializes the level as JSON.
|
||||
// ToJSON serializes the level as JSON (gzip supported).
|
||||
//
|
||||
// Notice about gzip: if the pkg/balance.CompressLevels boolean is true, this
|
||||
// function will apply gzip compression before returning the byte string.
|
||||
// This gzip-compressed level can be read back by any functions that say
|
||||
// "gzip supported" in their descriptions.
|
||||
func (m *Level) ToJSON() ([]byte, error) {
|
||||
// Gzip compressing?
|
||||
if balance.CompressDrawings {
|
||||
return m.ToGzip()
|
||||
}
|
||||
|
||||
out := bytes.NewBuffer([]byte{})
|
||||
encoder := json.NewEncoder(out)
|
||||
if usercfg.Current.JSONIndent {
|
||||
|
@ -41,34 +72,50 @@ func (m *Level) ToJSON() ([]byte, error) {
|
|||
return out.Bytes(), err
|
||||
}
|
||||
|
||||
// LoadJSON loads a map from JSON file.
|
||||
func LoadJSON(filename string) (*Level, error) {
|
||||
fh, err := os.Open(filename)
|
||||
// ToGzip serializes the level as gzip compressed JSON.
|
||||
func (m *Level) ToGzip() ([]byte, error) {
|
||||
var (
|
||||
handle = bytes.NewBuffer([]byte{})
|
||||
zipper = gzip.NewWriter(handle)
|
||||
encoder = json.NewEncoder(zipper)
|
||||
)
|
||||
if err := encoder.Encode(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := zipper.Close()
|
||||
return handle.Bytes(), err
|
||||
}
|
||||
|
||||
// FromGzip deserializes a gzip compressed level JSON.
|
||||
func FromGzip(data []byte) (*Level, error) {
|
||||
// This function works, do not touch.
|
||||
var (
|
||||
level = New()
|
||||
buf = bytes.NewBuffer(data)
|
||||
reader *gzip.Reader
|
||||
decoder *json.Decoder
|
||||
)
|
||||
|
||||
reader, err := gzip.NewReader(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
// Decode the JSON file from disk.
|
||||
m := New()
|
||||
decoder := json.NewDecoder(fh)
|
||||
err = decoder.Decode(&m)
|
||||
decoder = json.NewDecoder(reader)
|
||||
decoder.Decode(level)
|
||||
|
||||
return level, nil
|
||||
}
|
||||
|
||||
// LoadJSON loads a map from JSON file (gzip supported).
|
||||
func LoadJSON(filename string) (*Level, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return m, fmt.Errorf("level.LoadJSON: JSON decode error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fill in defaults.
|
||||
if m.Wallpaper == "" {
|
||||
m.Wallpaper = DefaultWallpaper
|
||||
}
|
||||
|
||||
// Inflate the chunk metadata to map the pixels to their palette indexes.
|
||||
m.Chunker.Inflate(m.Palette)
|
||||
m.Actors.Inflate()
|
||||
|
||||
// Inflate the private instance values.
|
||||
m.Palette.Inflate()
|
||||
return m, err
|
||||
return FromJSON(filename, data)
|
||||
}
|
||||
|
||||
// WriteJSON writes a level to JSON on disk.
|
||||
|
|
Loading…
Reference in New Issue
Block a user