Files
ordoor/internal/assetstore/assetstore.go

168 lines
3.9 KiB
Go
Raw Normal View History

package assetstore
import (
"fmt"
2020-06-01 01:24:44 +01:00
"image/color"
"io/ioutil"
"os"
"path/filepath"
"strings"
2020-03-22 15:37:48 +00:00
"github.com/hajimehoshi/ebiten"
2020-06-01 01:08:53 +01:00
"code.ur.gs/lupine/ordoor/internal/config"
2020-03-22 15:37:48 +00:00
"code.ur.gs/lupine/ordoor/internal/data"
2020-04-16 15:30:47 +01:00
"code.ur.gs/lupine/ordoor/internal/idx"
2020-06-01 01:24:44 +01:00
"code.ur.gs/lupine/ordoor/internal/palettes"
)
type entryMap map[string]map[string]string
// type AssetStore is responsible for lazily loading game data when it is
// required. Applications shouldn't need to do anything except set one of these
2020-06-01 01:08:53 +01:00
// up, pointing at the game dir root, to access all assets for that game.
//
// Assets should be loaded on-demand to keep memory costs as low as possible.
// Cross-platform differences such as filename case sensitivity are also dealt
// with here.
//
// We assume the directory is read-only. You can run Refresh() if you make a
// change.
2020-06-01 01:08:53 +01:00
//
// To mix assets from different games, either construct a synthetic directory
// or instantiate two separate asset stores.
type AssetStore struct {
RootDir string
2020-06-01 01:24:44 +01:00
Palette color.Palette
// Case-insensitive file lookup.
// {"":{"anim":"Anim", "obj":"Obj", ...}, "anim":{ "warhammer.ani":"WarHammer.ani" }, ...}
entries entryMap
// These members are used to store things we've already loaded
2020-04-16 15:30:47 +01:00
aniObj *Object
2020-04-10 19:55:16 +01:00
cursorObj *Object
2020-04-16 15:30:47 +01:00
cursors map[CursorName]*Cursor
fonts map[string]*Font
generic *data.Generic
idx *idx.Idx
images map[string]*ebiten.Image
2020-04-16 15:30:47 +01:00
maps map[string]*Map
menus map[string]*Menu
objs map[string]*Object
sets map[string]*Set
sounds map[string]*Sound
strings *data.I18n
}
// New returns a new AssetStore
2020-06-01 01:08:53 +01:00
func New(engine *config.Engine) (*AssetStore, error) {
if engine == nil {
return nil, fmt.Errorf("Unconfigured engine passed to assetstore")
}
2020-06-01 01:24:44 +01:00
palette, ok := palettes.Get(engine.Palette)
if !ok {
return nil, fmt.Errorf("Couldn't find palette %q for engine", engine.Palette)
}
store := &AssetStore{
2020-06-01 01:08:53 +01:00
RootDir: engine.DataDir,
2020-06-01 01:24:44 +01:00
Palette: palette,
}
// fill entryMap
if err := store.Refresh(); err != nil {
return nil, err
}
return store, nil
}
func (a *AssetStore) Refresh() error {
rootEntries, err := processDir(a.RootDir)
if err != nil {
return fmt.Errorf("failed to process %v: %v", a.RootDir, err)
}
newEntryMap := make(entryMap, len(rootEntries))
2020-06-01 01:08:53 +01:00
newEntryMap[""] = rootEntries
for lower, natural := range rootEntries {
path := filepath.Join(a.RootDir, natural)
fi, err := os.Stat(path)
if err != nil {
return fmt.Errorf("Failed to stat %v: %v", path, err)
}
if fi.IsDir() {
entries, err := processDir(path)
if err != nil {
return fmt.Errorf("Failed to process %v: %v", path, err)
}
newEntryMap[lower] = entries
}
}
// Refresh
2020-04-16 15:30:47 +01:00
a.aniObj = nil
2020-04-10 19:55:16 +01:00
a.cursorObj = nil
a.cursors = make(map[CursorName]*Cursor)
a.entries = newEntryMap
a.fonts = make(map[string]*Font)
2020-04-16 15:30:47 +01:00
a.idx = nil
a.images = make(map[string]*ebiten.Image)
a.maps = make(map[string]*Map)
a.menus = make(map[string]*Menu)
a.objs = make(map[string]*Object)
a.sets = make(map[string]*Set)
a.sounds = make(map[string]*Sound)
2020-03-22 15:37:48 +00:00
a.strings = nil
return nil
}
func (a *AssetStore) lookup(name, ext string, dirs ...string) (string, error) {
var filename string
if ext != "" {
filename = canonical(name + "." + ext)
} else {
filename = canonical(name)
}
for _, dir := range dirs {
dir = canonical(dir)
if base, ok := a.entries[dir]; ok {
if file, ok := base[filename]; ok {
2020-06-01 01:08:53 +01:00
actualDir := a.entries[""][dir]
return filepath.Join(a.RootDir, actualDir, file), nil
}
}
}
return "", fmt.Errorf("file %q does not exist", filename)
}
func canonical(s string) string {
return strings.ToLower(s)
}
func processDir(dir string) (map[string]string, error) {
entries, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
out := make(map[string]string, len(entries))
for _, entry := range entries {
if entry.Name() == "." || entry.Name() == ".." {
continue
}
out[canonical(entry.Name())] = entry.Name()
}
return out, nil
}