2020-03-19 22:24:21 +00:00
|
|
|
package assetstore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
RootDir = "" // Used in the entryMap for entries pertaining to the root dir
|
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
// up, pointing at the game dir root, to access all assets.
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
type AssetStore struct {
|
|
|
|
RootDir string
|
|
|
|
|
|
|
|
// 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-03-21 23:45:51 +00:00
|
|
|
maps map[string]*Map
|
2020-03-22 02:58:52 +00:00
|
|
|
menus map[string]*Menu
|
2020-03-21 23:45:51 +00:00
|
|
|
objs map[string]*Object
|
|
|
|
sets map[string]*Set
|
|
|
|
sounds map[string]*Sound
|
2020-03-19 22:24:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a new AssetStore
|
|
|
|
func New(dir string) (*AssetStore, error) {
|
|
|
|
store := &AssetStore{
|
|
|
|
RootDir: dir,
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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))
|
|
|
|
newEntryMap[RootDir] = 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
|
|
|
|
a.entries = newEntryMap
|
|
|
|
a.maps = make(map[string]*Map)
|
2020-03-22 02:58:52 +00:00
|
|
|
a.menus = make(map[string]*Menu)
|
2020-03-19 22:24:21 +00:00
|
|
|
a.objs = make(map[string]*Object)
|
|
|
|
a.sets = make(map[string]*Set)
|
2020-03-21 23:45:51 +00:00
|
|
|
a.sounds = make(map[string]*Sound)
|
2020-03-19 22:24:21 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *AssetStore) lookup(name, ext string, dirs ...string) (string, error) {
|
2020-03-22 02:58:52 +00:00
|
|
|
var filename string
|
|
|
|
if ext != "" {
|
|
|
|
filename = canonical(name + "." + ext)
|
|
|
|
} else {
|
|
|
|
filename = canonical(name)
|
|
|
|
}
|
2020-03-19 22:24:21 +00:00
|
|
|
|
|
|
|
for _, dir := range dirs {
|
|
|
|
dir = canonical(dir)
|
|
|
|
if base, ok := a.entries[dir]; ok {
|
|
|
|
if file, ok := base[filename]; ok {
|
|
|
|
actualDir := a.entries[RootDir][dir]
|
|
|
|
return filepath.Join(a.RootDir, actualDir, file), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", os.ErrNotExist
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|