1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2025-02-04 23:56:40 -05:00
OpenDiablo2/d2core/d2systems/asset_loader.go

426 lines
13 KiB
Go
Raw Normal View History

package d2systems
import (
2020-10-12 17:35:11 -04:00
"io"
"github.com/gravestench/akara"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
2020-10-12 17:35:11 -04:00
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2animdata"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dat"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dc6"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2pl2"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
)
const (
assetCacheBudget = 1024
assetCacheEntryWeight = 1 // may want to make different weights for different asset types
)
const (
logPrefixAssetLoader = "Asset Loader System"
)
2020-10-12 17:35:11 -04:00
// NewAssetLoader creates a new asset loader instance
func NewAssetLoader() *AssetLoaderSystem {
2020-10-12 17:35:11 -04:00
// we are going to check entities that dont yet have loaded asset types
filesToLoad := akara.NewFilter().
Require(d2components.FilePath). // we want to process entities with these file components
2020-10-12 17:35:11 -04:00
Require(d2components.FileType).
Require(d2components.FileHandle).
Forbid(d2components.FileSource). // but we forbid files that are already loaded
2020-10-12 17:35:11 -04:00
Forbid(d2components.GameConfig).
Forbid(d2components.StringTable).
Forbid(d2components.DataDictionary).
Forbid(d2components.Palette).
Forbid(d2components.PaletteTransform).
Forbid(d2components.Cof).
Forbid(d2components.Dc6).
Forbid(d2components.Dcc).
Forbid(d2components.Ds1).
Forbid(d2components.Dt1).
Forbid(d2components.Wav).
Forbid(d2components.AnimData).
Build()
2020-10-12 17:35:11 -04:00
fileSources := akara.NewFilter().
Require(d2components.FileSource).
Build()
assetLoader := &AssetLoaderSystem{
BaseSubscriberSystem: akara.NewBaseSubscriberSystem(filesToLoad, fileSources),
cache: d2cache.CreateCache(assetCacheBudget).(*d2cache.Cache),
Logger: d2util.NewLogger(),
}
assetLoader.SetPrefix(logPrefixAssetLoader)
return assetLoader
}
2020-10-12 17:35:11 -04:00
var _ akara.System = &AssetLoaderSystem{}
// AssetLoaderSystem is responsible for parsing data from file handles into various structs, like COF or DC6
type AssetLoaderSystem struct {
*akara.BaseSubscriberSystem
*d2util.Logger
fileSub *akara.Subscription
sourceSub *akara.Subscription
cache *d2cache.Cache
*d2components.FilePathMap
*d2components.FileTypeMap
*d2components.FileHandleMap
*d2components.FileSourceMap
*d2components.StringTableMap
*d2components.FontTableMap
*d2components.DataDictionaryMap
*d2components.PaletteMap
*d2components.PaletteTransformMap
*d2components.CofMap
*d2components.Dc6Map
*d2components.DccMap
*d2components.Ds1Map
*d2components.Dt1Map
*d2components.WavMap
*d2components.AnimDataMap
}
// Init injects component maps related to various asset types
func (m *AssetLoaderSystem) Init(_ *akara.World) {
m.Info("initializing ...")
m.fileSub = m.Subscriptions[0]
m.sourceSub = m.Subscriptions[1]
// try to inject the components we require, then cast the returned
// abstract ComponentMap back to the concrete implementation
m.FilePathMap = m.InjectMap(d2components.FilePath).(*d2components.FilePathMap)
m.FileTypeMap = m.InjectMap(d2components.FileType).(*d2components.FileTypeMap)
m.FileHandleMap = m.InjectMap(d2components.FileHandle).(*d2components.FileHandleMap)
m.FileSourceMap = m.InjectMap(d2components.FileSource).(*d2components.FileSourceMap)
m.StringTableMap = m.InjectMap(d2components.StringTable).(*d2components.StringTableMap)
m.DataDictionaryMap = m.InjectMap(d2components.DataDictionary).(*d2components.DataDictionaryMap)
m.PaletteMap = m.InjectMap(d2components.Palette).(*d2components.PaletteMap)
m.PaletteTransformMap = m.InjectMap(d2components.PaletteTransform).(*d2components.PaletteTransformMap)
m.FontTableMap = m.InjectMap(d2components.FontTable).(*d2components.FontTableMap)
m.CofMap = m.InjectMap(d2components.Cof).(*d2components.CofMap)
m.Dc6Map = m.InjectMap(d2components.Dc6).(*d2components.Dc6Map)
m.DccMap = m.InjectMap(d2components.Dcc).(*d2components.DccMap)
m.Ds1Map = m.InjectMap(d2components.Ds1).(*d2components.Ds1Map)
m.Dt1Map = m.InjectMap(d2components.Dt1).(*d2components.Dt1Map)
m.WavMap = m.InjectMap(d2components.Wav).(*d2components.WavMap)
m.AnimDataMap = m.InjectMap(d2components.AnimData).(*d2components.AnimDataMap)
}
// Update processes all of the Entities in the subscription of file entities that need to be processed
func (m *AssetLoaderSystem) Update() {
for _, eid := range m.fileSub.GetEntities() {
2020-10-12 17:35:11 -04:00
m.loadAsset(eid)
}
}
2020-10-12 17:35:11 -04:00
func (m *AssetLoaderSystem) loadAsset(id akara.EID) {
// make sure everything is kosher
fp, found := m.GetFilePath(id)
if !found {
m.Errorf("filepath component not found for entity %d", id)
return
}
ft, found := m.GetFileType(id)
if !found {
m.Errorf("filetype component not found for entity %d", id)
return
}
fh, found := m.GetFileHandle(id)
if !found {
m.Errorf("filehandle component not found for entity %d", id)
return
}
// try to pull from the cache and assign to the given entity id
if found := m.assignFromCache(id, fp.Path, ft.Type); found {
m.Debugf("Retrieving %s from cache", fp.Path)
return
}
// make sure to seek back to 0 if the filehandle was cached
_, _ = fh.Data.Seek(0, 0)
data, buf := make([]byte, 0), make([]byte, 16)
2020-10-12 17:35:11 -04:00
// read, parse, and cache the data
for {
numRead, err := fh.Data.Read(buf)
data = append(data, buf[:numRead]...)
2020-10-12 17:35:11 -04:00
if numRead < 1 || err != nil {
break
}
}
2020-10-28 18:49:49 -04:00
m.parseAndCache(id, fp.Path, ft.Type, data)
2020-10-12 17:35:11 -04:00
}
func (m *AssetLoaderSystem) assignFromCache(id akara.EID, path string, t d2enum.FileType) bool {
entry, found := m.cache.Retrieve(path)
if !found {
return found
}
// if we found what we're looking for, create the appropriate component and assign what we retrieved
switch t {
case d2enum.FileTypeStringTable:
m.AddStringTable(id).TextDictionary = entry.(*d2tbl.TextDictionary)
case d2enum.FileTypeFontTable:
m.AddFontTable(id).Data = entry.([]byte)
case d2enum.FileTypeDataDictionary:
m.AddDataDictionary(id).DataDictionary = entry.(*d2txt.DataDictionary)
case d2enum.FileTypePalette:
m.AddPalette(id).Palette = entry.(d2interface.Palette)
case d2enum.FileTypePaletteTransform:
m.AddPaletteTransform(id).Transform = entry.(*d2pl2.PL2)
case d2enum.FileTypeCOF:
m.AddCof(id).COF = entry.(*d2cof.COF)
case d2enum.FileTypeDC6:
m.AddDc6(id).DC6 = entry.(*d2dc6.DC6)
case d2enum.FileTypeDCC:
m.AddDcc(id).DCC = entry.(*d2dcc.DCC)
case d2enum.FileTypeDS1:
m.AddDs1(id).DS1 = entry.(*d2ds1.DS1)
case d2enum.FileTypeDT1:
m.AddDt1(id).DT1 = entry.(*d2dt1.DT1)
case d2enum.FileTypeWAV:
m.AddWav(id).Data = entry.(d2interface.DataStream)
case d2enum.FileTypeD2:
m.AddAnimData(id).AnimationData = entry.(*d2animdata.AnimationData)
}
return found
}
//nolint:gocyclo // this big switch statement is unfortunate, but necessary
2020-10-28 18:49:49 -04:00
func (m *AssetLoaderSystem) parseAndCache(id akara.EID, path string, t d2enum.FileType, data []byte) {
go func() {
switch t {
case d2enum.FileTypeStringTable:
m.Infof("Loading string table: %s", path)
m.loadStringTable(id, path, data)
2020-10-28 18:49:49 -04:00
case d2enum.FileTypeFontTable:
m.Infof("Loading font table: %s", path)
m.loadFontTable(id, path, data)
2020-10-28 18:49:49 -04:00
case d2enum.FileTypeDataDictionary:
m.Infof("Loading data dictionary: %s", path)
m.loadDataDictionary(id, path, data)
2020-10-28 18:49:49 -04:00
case d2enum.FileTypePalette:
m.Infof("Loading palette: %s", path)
if err := m.loadPalette(id, path, data); err != nil {
m.Error(err.Error())
}
2020-10-28 18:49:49 -04:00
case d2enum.FileTypePaletteTransform:
m.Infof("Loading palette transform: %s", path)
if err := m.loadPaletteTransform(id, path, data); err != nil {
m.Error(err.Error())
}
2020-10-28 18:49:49 -04:00
case d2enum.FileTypeCOF:
m.Infof("Loading COF: %s", path)
if err := m.loadCOF(id, path, data); err != nil {
m.Error(err.Error())
}
2020-10-28 18:49:49 -04:00
case d2enum.FileTypeDC6:
m.Infof("Loading DC6: %s", path)
if err := m.loadDC6(id, path, data); err != nil {
m.Error(err.Error())
}
2020-10-28 18:49:49 -04:00
case d2enum.FileTypeDCC:
m.Infof("Loading DCC: %s", path)
if err := m.loadDCC(id, path, data); err != nil {
m.Error(err.Error())
}
2020-10-28 18:49:49 -04:00
case d2enum.FileTypeDS1:
m.Infof("Loading DS1: %s", path)
if err := m.loadDS1(id, path, data); err != nil {
m.Error(err.Error())
}
2020-10-28 18:49:49 -04:00
case d2enum.FileTypeDT1:
m.Infof("Loading DT1: %s", path)
if err := m.loadDT1(id, path, data); err != nil {
m.Error(err.Error())
}
2020-10-28 18:49:49 -04:00
case d2enum.FileTypeWAV:
m.Infof("Loading WAV: %s", path)
fh, found := m.GetFileHandle(id)
2020-10-28 18:49:49 -04:00
if !found {
return
}
m.loadWAV(id, path, fh.Data)
2020-10-28 18:49:49 -04:00
case d2enum.FileTypeD2:
m.Infof("Loading animation data: %s", path)
if err := m.loadAnimData(id, path, data); err != nil {
m.Error(err.Error())
}
2020-10-12 17:35:11 -04:00
}
2020-10-28 18:49:49 -04:00
}()
2020-10-12 17:35:11 -04:00
}
func (m *AssetLoaderSystem) loadStringTable(id akara.EID, path string, data []byte) {
2020-10-12 17:35:11 -04:00
txt := d2tbl.LoadTextDictionary(data)
loaded := &txt
m.AddStringTable(id).TextDictionary = loaded
if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
}
func (m *AssetLoaderSystem) loadFontTable(id akara.EID, path string, data []byte) {
m.AddFontTable(id).Data = data
if cacheErr := m.cache.Insert(path, data, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
2020-10-12 17:35:11 -04:00
}
func (m *AssetLoaderSystem) loadDataDictionary(id akara.EID, path string, data []byte) {
loaded := d2txt.LoadDataDictionary(data)
m.AddDataDictionary(id).DataDictionary = loaded
if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
2020-10-12 17:35:11 -04:00
}
func (m *AssetLoaderSystem) loadPalette(id akara.EID, path string, data []byte) error {
2020-10-12 17:35:11 -04:00
loaded, err := d2dat.Load(data)
if err == nil {
m.AddPalette(id).Palette = loaded
if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
2020-10-12 17:35:11 -04:00
}
return err
}
func (m *AssetLoaderSystem) loadPaletteTransform(id akara.EID, path string, data []byte) error {
2020-10-12 17:35:11 -04:00
loaded, err := d2pl2.Load(data)
if err == nil {
m.AddPaletteTransform(id).Transform = loaded
if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
}
2020-10-12 17:35:11 -04:00
return err
}
func (m *AssetLoaderSystem) loadCOF(id akara.EID, path string, data []byte) error {
2020-10-12 17:35:11 -04:00
loaded, err := d2cof.Load(data)
if err == nil {
m.AddCof(id).COF = loaded
if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
2020-10-12 17:35:11 -04:00
}
return err
}
func (m *AssetLoaderSystem) loadDC6(id akara.EID, path string, data []byte) error {
2020-10-12 17:35:11 -04:00
loaded, err := d2dc6.Load(data)
if err == nil {
m.AddDc6(id).DC6 = loaded
if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
2020-10-12 17:35:11 -04:00
}
return err
}
func (m *AssetLoaderSystem) loadDCC(id akara.EID, path string, data []byte) error {
2020-10-12 17:35:11 -04:00
loaded, err := d2dcc.Load(data)
if err == nil {
m.AddDcc(id).DCC = loaded
if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
2020-10-12 17:35:11 -04:00
}
return err
}
func (m *AssetLoaderSystem) loadDS1(id akara.EID, path string, data []byte) error {
2020-10-12 17:35:11 -04:00
loaded, err := d2ds1.LoadDS1(data)
if err == nil {
m.AddDs1(id).DS1 = loaded
if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
2020-10-12 17:35:11 -04:00
}
return err
}
func (m *AssetLoaderSystem) loadDT1(id akara.EID, path string, data []byte) error {
2020-10-12 17:35:11 -04:00
loaded, err := d2dt1.LoadDT1(data)
if err == nil {
m.AddDt1(id).DT1 = loaded
if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
2020-10-12 17:35:11 -04:00
}
return err
}
func (m *AssetLoaderSystem) loadWAV(id akara.EID, path string, seeker io.ReadSeeker) {
component := m.AddWav(id)
2020-10-12 17:35:11 -04:00
component.Data = seeker
if cacheErr := m.cache.Insert(path, seeker, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
2020-10-12 17:35:11 -04:00
}
func (m *AssetLoaderSystem) loadAnimData(id akara.EID, path string, data []byte) error {
2020-10-12 17:35:11 -04:00
loaded, err := d2animdata.Load(data)
if err == nil {
m.AddAnimData(id).AnimationData = loaded
if cacheErr := m.cache.Insert(path, loaded, assetCacheEntryWeight); cacheErr != nil {
m.Error(cacheErr.Error())
}
2020-10-12 17:35:11 -04:00
}
return err
}