From bdf3a2e75d13b35b83b44260aae2fa25eb518f69 Mon Sep 17 00:00:00 2001 From: gravestench Date: Sat, 14 Nov 2020 09:52:07 -0800 Subject: [PATCH] adding d2util.Loggers instances to existing systems --- d2core/d2systems/asset_loader.go | 32 ++++- d2core/d2systems/file_handle_resolver.go | 31 +++-- d2core/d2systems/file_source_resolver.go | 37 +++++- d2core/d2systems/file_type_resolver.go | 15 ++- d2core/d2systems/game_bootstrap.go | 146 +++++++++++++---------- d2core/d2systems/game_config.go | 78 ++++++------ d2core/d2systems/integration_test.go | 25 ++-- d2core/d2systems/movement.go | 12 ++ d2core/d2systems/render.go | 78 +++++++----- d2core/d2systems/timescale.go | 18 ++- go.mod | 2 +- go.sum | 9 +- 12 files changed, 307 insertions(+), 176 deletions(-) diff --git a/d2core/d2systems/asset_loader.go b/d2core/d2systems/asset_loader.go index ce245dd0..2d19fd58 100644 --- a/d2core/d2systems/asset_loader.go +++ b/d2core/d2systems/asset_loader.go @@ -1,6 +1,7 @@ package d2systems import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "io" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -29,14 +30,18 @@ const ( assetCacheEntryWeight = 1 // may want to make different weights for different asset types ) +const ( + LogPrefixAssetLoader = "Asset Loader System" +) + // NewAssetLoader creates a new asset loader instance func NewAssetLoader() *AssetLoaderSystem { // we are going to check entities that dont yet have loaded asset types filesToLoad := akara.NewFilter(). - Require(d2components.FilePath). + Require(d2components.FilePath). // we want to process entities with these file components Require(d2components.FileType). Require(d2components.FileHandle). - Forbid(d2components.FileSource). + Forbid(d2components.FileSource). // but we forbid files that are already loaded Forbid(d2components.GameConfig). Forbid(d2components.StringTable). Forbid(d2components.DataDictionary). @@ -55,16 +60,22 @@ func NewAssetLoader() *AssetLoaderSystem { Require(d2components.FileSource). Build() - return &AssetLoaderSystem{ + assetLoader := &AssetLoaderSystem{ SubscriberSystem: akara.NewSubscriberSystem(filesToLoad, fileSources), cache: d2cache.CreateCache(assetCacheBudget).(*d2cache.Cache), + Logger: d2util.NewLogger(), } + + assetLoader.SetPrefix(LogPrefixAssetLoader) + + return assetLoader } var _ akara.System = &AssetLoaderSystem{} type AssetLoaderSystem struct { *akara.SubscriberSystem + *d2util.Logger fileSub *akara.Subscription sourceSub *akara.Subscription cache *d2cache.Cache @@ -95,6 +106,8 @@ func (m *AssetLoaderSystem) Init(world *akara.World) { return } + m.Info("initializing ...") + for subIdx := range m.Subscriptions { m.Subscriptions[subIdx] = m.AddSubscription(m.Subscriptions[subIdx].Filter) } @@ -130,29 +143,37 @@ func (m *AssetLoaderSystem) Process() { } func (m *AssetLoaderSystem) loadAsset(id akara.EID) { + // make sure everything is kosher fp, found := m.filePaths.GetFilePath(id) if !found { + m.Errorf("filepath component not found for entity %d", id) return } ft, found := m.fileTypes.GetFileType(id) if !found { + m.Errorf("filetype component not found for entity %d", id) return } fh, found := m.fileHandles.GetFileHandle(id) if !found { + m.Errorf("filehandle component not found for entity %d", id) return } - if found := m.pullFromCache(id, fp.Path, ft.Type); found { + // 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) + // read, parse, and cache the data for { numRead, err := fh.Data.Read(buf) data = append(data, buf[:numRead]...) @@ -165,12 +186,13 @@ func (m *AssetLoaderSystem) loadAsset(id akara.EID) { m.parseAndCache(id, fp.Path, ft.Type, data) } -func (m *AssetLoaderSystem) pullFromCache(id akara.EID, path string, t d2enum.FileType) bool { +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.stringTables.AddStringTable(id).TextDictionary = entry.(*d2tbl.TextDictionary) diff --git a/d2core/d2systems/file_handle_resolver.go b/d2core/d2systems/file_handle_resolver.go index 4d1786d6..24b217de 100644 --- a/d2core/d2systems/file_handle_resolver.go +++ b/d2core/d2systems/file_handle_resolver.go @@ -1,6 +1,7 @@ package d2systems import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "strings" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -24,6 +25,10 @@ const ( fileHandleCacheEntryWeight = 1 ) +const ( + logPrefixFileHandleResolver = "File Handle Resolver" +) + func NewFileHandleResolver() *FileHandleResolutionSystem { // this filter is for entities that have a file path and file type but no file handle. filesToSource := akara.NewFilter(). @@ -37,18 +42,20 @@ func NewFileHandleResolver() *FileHandleResolutionSystem { RequireOne(d2components.FileSource). Build() - configsToUse := akara.NewFilter(). - Require(d2components.GameConfig). - Build() - - return &FileHandleResolutionSystem{ - SubscriberSystem: akara.NewSubscriberSystem(filesToSource, sourcesToUse, configsToUse), + fhr := &FileHandleResolutionSystem{ + SubscriberSystem: akara.NewSubscriberSystem(filesToSource, sourcesToUse), cache: d2cache.CreateCache(fileHandleCacheBudget).(*d2cache.Cache), + Logger: d2util.NewLogger(), } + + fhr.SetPrefix(logPrefixFileHandleResolver) + + return fhr } type FileHandleResolutionSystem struct { *akara.SubscriberSystem + *d2util.Logger cache *d2cache.Cache filesToLoad *akara.Subscription sourcesToUse *akara.Subscription @@ -62,15 +69,17 @@ type FileHandleResolutionSystem struct { func (m *FileHandleResolutionSystem) Init(world *akara.World) { m.World = world - for subIdx := range m.Subscriptions { - m.Subscriptions[subIdx] = m.AddSubscription(m.Subscriptions[subIdx].Filter) - } - if world == nil { m.SetActive(false) return } + for subIdx := range m.Subscriptions { + m.Subscriptions[subIdx] = m.AddSubscription(m.Subscriptions[subIdx].Filter) + } + + m.Info("initializing ...") + m.filesToLoad = m.Subscriptions[0] m.sourcesToUse = m.Subscriptions[1] @@ -169,7 +178,7 @@ func (m *FileHandleResolutionSystem) loadFileWithSource(fileID, sourceID akara.E } } - //fmt.Printf("%s -> %s\n", sourceFp.Path, fp.Path) + m.Infof("resolved `%s` with source `%s`", fp.Path, sourceFp.Path) component := m.fileHandles.AddFileHandle(fileID) component.Data = data diff --git a/d2core/d2systems/file_source_resolver.go b/d2core/d2systems/file_source_resolver.go index db9612b8..8ce52f98 100644 --- a/d2core/d2systems/file_source_resolver.go +++ b/d2core/d2systems/file_source_resolver.go @@ -1,6 +1,7 @@ package d2systems import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "os" "path/filepath" "strings" @@ -13,6 +14,10 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" ) +const ( + logPrefixFileSourceResolver = "File Source Resolver" +) + func NewFileSourceResolver() *FileSourceResolver { // subscribe to entities with a file type and file path, but no file source type filesToCheck := akara.NewFilter(). @@ -21,13 +26,19 @@ func NewFileSourceResolver() *FileSourceResolver { Forbid(d2components.FileSource). Build() - return &FileSourceResolver{ + fsr := &FileSourceResolver{ SubscriberSystem: akara.NewSubscriberSystem(filesToCheck), + Logger: d2util.NewLogger(), } + + fsr.SetPrefix(logPrefixFileSourceResolver) + + return fsr } type FileSourceResolver struct { *akara.SubscriberSystem + *d2util.Logger fileSub *akara.Subscription filePaths *d2components.FilePathMap fileTypes *d2components.FileTypeMap @@ -43,6 +54,8 @@ func (m *FileSourceResolver) Init(world *akara.World) { return } + m.Info("initializing ...") + for subIdx := range m.Subscriptions { m.Subscriptions[subIdx] = m.AddSubscription(m.Subscriptions[subIdx].Filter) } @@ -60,13 +73,12 @@ func (m *FileSourceResolver) Init(world *akara.World) { func (m *FileSourceResolver) Process() { for subIdx := range m.Subscriptions { for _, sourceEntityID := range m.Subscriptions[subIdx].GetEntities() { - m.ProcessEntity(sourceEntityID) + m.processSourceEntity(sourceEntityID) } } } -// ProcessEntity updates an individual entity in the system -func (m *FileSourceResolver) ProcessEntity(id akara.EID) { +func (m *FileSourceResolver) processSourceEntity(id akara.EID) { fp, found := m.filePaths.GetFilePath(id) if !found { return @@ -79,9 +91,9 @@ func (m *FileSourceResolver) ProcessEntity(id akara.EID) { switch ft.Type { case d2enum.FileTypeUnknown: + m.Errorf("unknown file type for file `%s`", fp.Path) return case d2enum.FileTypeMPQ: - source := m.fileSources.AddFileSource(id) instance, err := m.makeMpqSource(fp.Path) if err != nil { @@ -89,9 +101,11 @@ func (m *FileSourceResolver) ProcessEntity(id akara.EID) { break } + source := m.fileSources.AddFileSource(id) + + m.Infof("creating MPQ source for `%s`", fp.Path) source.AbstractSource = instance case d2enum.FileTypeDirectory: - source := m.fileSources.AddFileSource(id) instance, err := m.makeFileSystemSource(fp.Path) if err != nil { @@ -99,6 +113,9 @@ func (m *FileSourceResolver) ProcessEntity(id akara.EID) { break } + source := m.fileSources.AddFileSource(id) + + m.Infof("creating FILESYSTEM source for `%s`", fp.Path) source.AbstractSource = instance } } @@ -125,6 +142,10 @@ func (s *fsSource) fullPath(path string) string { return filepath.Clean(filepath.Join(s.rootDir, path)) } +func (s *fsSource) Path() string { + return filepath.Clean(s.rootDir) +} + // mpq source func (m *FileSourceResolver) makeMpqSource(path string) (d2components.AbstractSource, error) { mpq, err := d2mpq.Load(path) @@ -157,3 +178,7 @@ func (s *mpqSource) cleanMpqPath(path string) string { return path } + +func (s *mpqSource) Path() string { + return filepath.Clean(s.mpq.Path()) +} diff --git a/d2core/d2systems/file_type_resolver.go b/d2core/d2systems/file_type_resolver.go index 42da159c..b55c5900 100644 --- a/d2core/d2systems/file_type_resolver.go +++ b/d2core/d2systems/file_type_resolver.go @@ -1,6 +1,7 @@ package d2systems import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "os" "path/filepath" "strings" @@ -12,6 +13,10 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" ) +const ( + logPrefixFileTypeResolver = "File Type Resolver" +) + // NewFileTypeResolver creates a new file type resolution system. func NewFileTypeResolver() *FileTypeResolver { // we subscribe only to entities that have a filepath @@ -21,9 +26,14 @@ func NewFileTypeResolver() *FileTypeResolver { Forbid(d2components.FileType). Build() - return &FileTypeResolver{ + ftr := &FileTypeResolver{ SubscriberSystem: akara.NewSubscriberSystem(filesToCheck), + Logger: d2util.NewLogger(), } + + ftr.SetPrefix(logPrefixFileTypeResolver) + + return ftr } // static check that FileTypeResolver implements the System interface @@ -36,6 +46,7 @@ var _ akara.System = &FileTypeResolver{} // from its subscription. type FileTypeResolver struct { *akara.SubscriberSystem + *d2util.Logger filesToCheck *akara.Subscription filePaths *d2components.FilePathMap fileTypes *d2components.FileTypeMap @@ -50,6 +61,8 @@ func (m *FileTypeResolver) Init(world *akara.World) { return } + m.Info("initializing ...") + for subIdx := range m.Subscriptions { m.Subscriptions[subIdx] = m.AddSubscription(m.Subscriptions[subIdx].Filter) } diff --git a/d2core/d2systems/game_bootstrap.go b/d2core/d2systems/game_bootstrap.go index a3de782c..03634f72 100644 --- a/d2core/d2systems/game_bootstrap.go +++ b/d2core/d2systems/game_bootstrap.go @@ -1,11 +1,11 @@ package d2systems import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" + "github.com/gravestench/akara" "os" "path" - "github.com/gravestench/akara" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" ) @@ -14,82 +14,89 @@ const ( configFileName = "config.json" ) +const ( + LoggerPrefixBootstrap = "Bootstrap System" +) + // static check that the game config system implements the system interface var _ akara.System = &GameBootstrapSystem{} func NewGameBootstrapSystem() *GameBootstrapSystem { // we are going to check entities that dont yet have loaded asset types - thingsToCheck := akara.NewFilter(). - Require(d2components.FilePath). - Require(d2components.FileType). - Require(d2components.FileHandle). - 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). + filesToCheck := akara.NewFilter(). + Require( // files that need to be loaded + d2components.FileType, + d2components.FileHandle, + d2components.FilePath, + ). + Forbid( // files which have been loaded + d2components.GameConfig, + d2components.StringTable, + d2components.DataDictionary, + d2components.Palette, + d2components.PaletteTransform, + d2components.Cof, + d2components.Dc6, + d2components.Dcc, + d2components.Ds1, + d2components.Dt1, + d2components.Wav, + d2components.AnimData, + ). Build() // we are interested in actual game config instances, too gameConfigs := akara.NewFilter().Require(d2components.GameConfig).Build() - gcs := &GameBootstrapSystem{ - SubscriberSystem: akara.NewSubscriberSystem(thingsToCheck, gameConfigs), - maps: struct { - gameConfigs *d2components.GameConfigMap - filePaths *d2components.FilePathMap - fileTypes *d2components.FileTypeMap - fileHandles *d2components.FileHandleMap - fileSources *d2components.FileSourceMap - }{}, + bootstrapSys := &GameBootstrapSystem{ + SubscriberSystem: akara.NewSubscriberSystem(filesToCheck, gameConfigs), + Logger: d2util.NewLogger(), } - return gcs + bootstrapSys.SetPrefix(LoggerPrefixBootstrap) + bootstrapSys.Debug("Created") + + return bootstrapSys } // GameBootstrapSystem is responsible for setting up the regular diablo2 game launch type GameBootstrapSystem struct { *akara.SubscriberSystem - filesToCheck *akara.Subscription - gameConfigs *akara.Subscription - maps struct { - gameConfigs *d2components.GameConfigMap - filePaths *d2components.FilePathMap - fileTypes *d2components.FileTypeMap - fileHandles *d2components.FileHandleMap - fileSources *d2components.FileSourceMap - } + *d2util.Logger + subscribedFiles *akara.Subscription + subscribedConfigs *akara.Subscription + *d2components.GameConfigMap + *d2components.FilePathMap + *d2components.FileTypeMap + *d2components.FileHandleMap + *d2components.FileSourceMap } func (m *GameBootstrapSystem) Init(world *akara.World) { m.World = world if world == nil { + m.Error("world is nil, deactivating.") m.SetActive(false) return } + m.Info("initializing ...") + for subIdx := range m.Subscriptions { m.Subscriptions[subIdx] = m.AddSubscription(m.Subscriptions[subIdx].Filter) } - m.filesToCheck = m.Subscriptions[0] - m.gameConfigs = m.Subscriptions[1] + m.subscribedFiles = m.Subscriptions[0] + m.subscribedConfigs = m.Subscriptions[1] // try to inject the components we require, then cast the returned // abstract ComponentMap back to the concrete implementation - m.maps.filePaths = world.InjectMap(d2components.FilePath).(*d2components.FilePathMap) - m.maps.fileTypes = world.InjectMap(d2components.FileType).(*d2components.FileTypeMap) - m.maps.fileHandles = world.InjectMap(d2components.FileHandle).(*d2components.FileHandleMap) - m.maps.fileSources = world.InjectMap(d2components.FileSource).(*d2components.FileSourceMap) - m.maps.gameConfigs = world.InjectMap(d2components.GameConfig).(*d2components.GameConfigMap) + m.GameConfigMap = world.InjectMap(d2components.GameConfig).(*d2components.GameConfigMap) + m.FilePathMap = world.InjectMap(d2components.FilePath).(*d2components.FilePathMap) + m.FileTypeMap = world.InjectMap(d2components.FileType).(*d2components.FileTypeMap) + m.FileHandleMap = world.InjectMap(d2components.FileHandle).(*d2components.FileHandleMap) + m.FileSourceMap = world.InjectMap(d2components.FileSource).(*d2components.FileSourceMap) m.bootstrap() } @@ -100,41 +107,58 @@ func (m *GameBootstrapSystem) bootstrap() { // we make two entities and assign file paths for the two directories that // we assume a config file may be inside of. These will be processed in the future by // the file type resolver system, and then the file source resolver system. At that point, - // there will be sources for these two directories that can resolve the config file. + // there will be sources for these two directories that can possibly resolve a config file. + // A new config file is created if one is not found. + + // make the two entities, these will be the file sources e1, e2 := m.NewEntity(), m.NewEntity() - fp1, fp2 := m.maps.filePaths.AddFilePath(e1), m.maps.filePaths.AddFilePath(e2) - // the od2 directory has the highest priority - fp1.Path = path.Dir(os.Args[0]) + // add file path components to these entities + fp1, fp2 := m.AddFilePath(e1), m.AddFilePath(e2) - // the user config directory is second highest - configDir, err := os.UserConfigDir() + // the first entity gets a filepath for the od2 directory, this one is checked first + // eg. if OD2 binary is in `~/src/OpenDiablo2/`, then this directory is checked first for a config file + cfgPath1 := path.Dir(os.Args[0]) + fp1.Path = cfgPath1 + m.Infof("setting up local directory %s for processing", cfgPath1) + + // now add the user config directory + // this directory on a linux machine would be something like `~/.config/OpenDiablo2/` + cfgPath2, err := os.UserConfigDir() if err == nil { - fp2.Path = path.Join(configDir, configDirectoryName) + fp2.Path = path.Join(cfgPath2, configDirectoryName) + m.Infof("setting up user config directory %s for processing", fp2.Path) } else { - // we couldn't find the directory + // we couldn't find the directory, destroy this entity + m.Error("user config directory not found, skipping") m.RemoveEntity(e2) } - // now we set up the config file to be loaded. this happens after the directories - // above are recognized as file sources. - e3 := m.NewEntity() - fp3 := m.maps.filePaths.AddFilePath(e3) - fp3.Path = configFileName + // now that we have set up where we look for config files, + // we need to make an entity for the config file we want to load + m.AddFilePath(m.NewEntity()).Path = configFileName + + // The actual processing of all of this happens when the world updates the systems. + // this process may take more than one iteration over the systems } func (m *GameBootstrapSystem) Process() { - configs := m.gameConfigs.GetEntities() + configs := m.subscribedConfigs.GetEntities() if len(configs) < 1 { return } - cfg, found := m.maps.gameConfigs.GetGameConfig(configs[0]) + m.Infof("found %d new configs to parse", len(configs)) + + firstConfigEntityID := configs[0] + cfg, found := m.GetGameConfig(firstConfigEntityID) if !found { return } m.initMpqSources(cfg) + + m.Info("game bootstrap complete, deactivating system") m.SetActive(false) // bootstrap is complete! } @@ -142,8 +166,10 @@ func (m *GameBootstrapSystem) initMpqSources(cfg *d2components.GameConfigCompone for _, mpqFileName := range cfg.MpqLoadOrder { fullMpqFilePath := path.Join(cfg.MpqPath, mpqFileName) + m.Infof("adding mpq: %s", fullMpqFilePath) + // make a new entity for the mpq file source - mpqSource := m.maps.filePaths.AddFilePath(m.NewEntity()) + mpqSource := m.AddFilePath(m.NewEntity()) mpqSource.Path = fullMpqFilePath } } diff --git a/d2core/d2systems/game_config.go b/d2core/d2systems/game_config.go index 6b811a5f..fed9d357 100644 --- a/d2core/d2systems/game_config.go +++ b/d2core/d2systems/game_config.go @@ -2,8 +2,8 @@ package d2systems import ( "encoding/json" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/gravestench/akara" @@ -13,9 +13,13 @@ import ( // static check that the game config system implements the system interface var _ akara.System = &GameConfigSystem{} +const ( + loggerPrefixGameConfig = "Game Config" +) + func NewGameConfigSystem() *GameConfigSystem { // we are going to check entities that dont yet have loaded asset types - thingsToCheck := akara.NewFilter(). + filesToCheck := akara.NewFilter(). Require(d2components.FilePath). Require(d2components.FileType). Require(d2components.FileHandle). @@ -34,20 +38,17 @@ func NewGameConfigSystem() *GameConfigSystem { Build() // we are interested in actual game config instances, too - gameConfigs := akara.NewFilter().Require(d2components.GameConfig).Build() + gameConfigs := akara.NewFilter(). + Require(d2components.GameConfig). + Build() gcs := &GameConfigSystem{ - SubscriberSystem: akara.NewSubscriberSystem(thingsToCheck, gameConfigs), - maps: struct { - gameConfigs *d2components.GameConfigMap - filePaths *d2components.FilePathMap - fileTypes *d2components.FileTypeMap - fileHandles *d2components.FileHandleMap - fileSources *d2components.FileSourceMap - dirty *d2components.DirtyMap - }{}, + SubscriberSystem: akara.NewSubscriberSystem(filesToCheck, gameConfigs), + Logger: d2util.NewLogger(), } + gcs.SetPrefix(loggerPrefixGameConfig) + return gcs } @@ -63,16 +64,15 @@ func NewGameConfigSystem() *GameConfigSystem { // this system either... type GameConfigSystem struct { *akara.SubscriberSystem + *d2util.Logger filesToCheck *akara.Subscription gameConfigs *akara.Subscription - maps struct { - gameConfigs *d2components.GameConfigMap - filePaths *d2components.FilePathMap - fileTypes *d2components.FileTypeMap - fileHandles *d2components.FileHandleMap - fileSources *d2components.FileSourceMap - dirty *d2components.DirtyMap - } + *d2components.GameConfigMap + *d2components.FilePathMap + *d2components.FileTypeMap + *d2components.FileHandleMap + *d2components.FileSourceMap + *d2components.DirtyMap } func (m *GameConfigSystem) Init(world *akara.World) { @@ -83,6 +83,8 @@ func (m *GameConfigSystem) Init(world *akara.World) { return } + m.Info("initializing ...") + for subIdx := range m.Subscriptions { m.Subscriptions[subIdx] = m.AddSubscription(m.Subscriptions[subIdx].Filter) } @@ -92,39 +94,26 @@ func (m *GameConfigSystem) Init(world *akara.World) { // try to inject the components we require, then cast the returned // abstract ComponentMap back to the concrete implementation - m.maps.filePaths = world.InjectMap(d2components.FilePath).(*d2components.FilePathMap) - m.maps.fileTypes = world.InjectMap(d2components.FileType).(*d2components.FileTypeMap) - m.maps.fileHandles = world.InjectMap(d2components.FileHandle).(*d2components.FileHandleMap) - m.maps.fileSources = world.InjectMap(d2components.FileSource).(*d2components.FileSourceMap) - m.maps.gameConfigs = world.InjectMap(d2components.GameConfig).(*d2components.GameConfigMap) - m.maps.dirty = world.InjectMap(d2components.Dirty).(*d2components.DirtyMap) + m.FilePathMap = world.InjectMap(d2components.FilePath).(*d2components.FilePathMap) + m.FileTypeMap = world.InjectMap(d2components.FileType).(*d2components.FileTypeMap) + m.FileHandleMap = world.InjectMap(d2components.FileHandle).(*d2components.FileHandleMap) + m.FileSourceMap = world.InjectMap(d2components.FileSource).(*d2components.FileSourceMap) + m.GameConfigMap = world.InjectMap(d2components.GameConfig).(*d2components.GameConfigMap) + m.DirtyMap = world.InjectMap(d2components.Dirty).(*d2components.DirtyMap) } func (m *GameConfigSystem) Process() { - m.clearDirty(m.gameConfigs.GetEntities()) m.checkForNewConfig(m.filesToCheck.GetEntities()) } -func (m *GameConfigSystem) clearDirty(entities []akara.EID) { - for _, eid := range entities { - dc, found := m.maps.dirty.GetDirty(eid) - if !found { - m.maps.dirty.AddDirty(eid) // adds it, but it's false - continue - } - - dc.IsDirty = false - } -} - func (m *GameConfigSystem) checkForNewConfig(entities []akara.EID) { for _, eid := range entities { - fp, found := m.maps.filePaths.GetFilePath(eid) + fp, found := m.GetFilePath(eid) if !found { continue } - ft, found := m.maps.fileTypes.GetFileType(eid) + ft, found := m.GetFileType(eid) if !found { continue } @@ -133,20 +122,21 @@ func (m *GameConfigSystem) checkForNewConfig(entities []akara.EID) { continue } + m.Info("loading config file ...") m.loadConfig(eid) } } func (m *GameConfigSystem) loadConfig(eid akara.EID) { - fh, found := m.maps.fileHandles.GetFileHandle(eid) + fh, found := m.GetFileHandle(eid) if !found { return } - gameConfig := m.maps.gameConfigs.AddGameConfig(eid) + gameConfig := m.AddGameConfig(eid) if err := json.NewDecoder(fh.Data).Decode(gameConfig); err != nil { - m.maps.gameConfigs.Remove(eid) + m.GameConfigMap.Remove(eid) return } } diff --git a/d2core/d2systems/integration_test.go b/d2core/d2systems/integration_test.go index c944f44c..1b126305 100644 --- a/d2core/d2systems/integration_test.go +++ b/d2core/d2systems/integration_test.go @@ -11,21 +11,13 @@ import ( func Test_integration(t *testing.T) { cfg := akara.NewWorldConfig() - bootstrap := NewGameBootstrapSystem() - fileTypeResolver := NewFileTypeResolver() - fileHandleResolver := NewFileHandleResolver() - fileSourceResolver := NewFileSourceResolver() - gameConfig := NewGameConfigSystem() - assetLoader := NewAssetLoader() - renderer := NewRenderSystem() - - cfg.With(fileTypeResolver). - With(fileSourceResolver). - With(fileHandleResolver). - With(gameConfig). - With(assetLoader). - With(renderer). - With(bootstrap) + cfg.With(NewFileTypeResolver()). + With(NewFileSourceResolver()). + With(NewFileHandleResolver()). + With(NewGameConfigSystem()). + With(NewAssetLoader()). + With(NewRenderSystem()). + With(NewGameBootstrapSystem()) world := akara.NewWorld(cfg) @@ -43,11 +35,8 @@ func Test_integration(t *testing.T) { mm, _ := world.ComponentManager.GetMap(d2components.Dc6) dc6map := mm.(*d2components.Dc6Map) - updateCount := 0 - for { world.Update(0) - updateCount++ _, found := dc6map.GetDc6(e1) if found { diff --git a/d2core/d2systems/movement.go b/d2core/d2systems/movement.go index a968580e..0c12be0a 100644 --- a/d2core/d2systems/movement.go +++ b/d2core/d2systems/movement.go @@ -1,6 +1,7 @@ package d2systems import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "time" "github.com/gravestench/akara" @@ -8,6 +9,10 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" ) +const ( + logPrefixMovementSystem = "Movement System" +) + // NewMovementSystem creates a movement system func NewMovementSystem() *MovementSystem { cfg := akara.NewFilter().Require(d2components.Position, d2components.Velocity) @@ -16,6 +21,7 @@ func NewMovementSystem() *MovementSystem { return &MovementSystem{ SubscriberSystem: akara.NewSubscriberSystem(filter), + Logger: d2util.NewLogger(), } } @@ -25,6 +31,7 @@ var _ akara.System = &MovementSystem{} // MovementSystem handles entity movement based on velocity and position components type MovementSystem struct { *akara.SubscriberSystem + *d2util.Logger positions *d2components.PositionMap velocities *d2components.VelocityMap } @@ -38,6 +45,8 @@ func (m *MovementSystem) Init(world *akara.World) { return } + m.Info("initializing ...") + for subIdx := range m.Subscriptions { m.Subscriptions[subIdx] = m.AddSubscription(m.Subscriptions[subIdx].Filter) } @@ -52,6 +61,9 @@ func (m *MovementSystem) Init(world *akara.World) { func (m *MovementSystem) Process() { for subIdx := range m.Subscriptions { entities := m.Subscriptions[subIdx].GetEntities() + + m.Infof("Processing movement for %d entities ...", len(entities)) + for entIdx := range entities { m.ProcessEntity(entities[entIdx]) } diff --git a/d2core/d2systems/render.go b/d2core/d2systems/render.go index 84d68db0..f675dcf4 100644 --- a/d2core/d2systems/render.go +++ b/d2core/d2systems/render.go @@ -1,10 +1,13 @@ package d2systems import ( + "errors" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2config" "time" "github.com/gravestench/akara" - "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/v2" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" @@ -13,6 +16,7 @@ import ( const ( gameTitle = "Open Diablo 2" + logPrefixRenderSystem = "Render System" ) // NewRenderSystem creates a movement system @@ -20,9 +24,14 @@ func NewRenderSystem() *RenderSystem { viewports := akara.NewFilter().Require(d2components.ViewPort).Build() gameConfigs := akara.NewFilter().Require(d2components.GameConfig).Build() - return &RenderSystem{ + r := &RenderSystem{ SubscriberSystem: akara.NewSubscriberSystem(viewports, gameConfigs), + Logger: d2util.NewLogger(), } + + r.SetPrefix(logPrefixRenderSystem) + + return r } // static check that RenderSystem implements the System interface @@ -31,12 +40,13 @@ var _ akara.System = &RenderSystem{} // RenderSystem handles entity movement based on velocity and position components type RenderSystem struct { *akara.SubscriberSystem + *d2util.Logger renderer d2interface.Renderer screenSurface d2interface.Surface viewports *akara.Subscription configs *akara.Subscription - configMap *d2components.GameConfigMap - viewportMap *d2components.ViewPortMap + *d2components.GameConfigMap + *d2components.ViewPortMap lastUpdate time.Time } @@ -49,6 +59,8 @@ func (m *RenderSystem) Init(world *akara.World) { return } + m.Info("initializing ...") + for subIdx := range m.Subscriptions { m.Subscriptions[subIdx] = m.AddSubscription(m.Subscriptions[subIdx].Filter) } @@ -58,8 +70,8 @@ func (m *RenderSystem) Init(world *akara.World) { // try to inject the components we require, then cast the returned // abstract ComponentMap back to the concrete implementation - m.configMap = m.InjectMap(d2components.GameConfig).(*d2components.GameConfigMap) - m.viewportMap = m.InjectMap(d2components.ViewPort).(*d2components.ViewPortMap) + m.GameConfigMap = m.InjectMap(d2components.GameConfig).(*d2components.GameConfigMap) + m.ViewPortMap = m.InjectMap(d2components.ViewPort).(*d2components.ViewPortMap) } // Process will create a renderer if it doesnt exist yet, @@ -68,14 +80,6 @@ func (m *RenderSystem) Process() { if m.renderer == nil { m.createRenderer() } - - if m.screenSurface == nil { - return - } - - for _, eid := range m.viewports.GetEntities() { - m.render(eid) - } } func (m *RenderSystem) createRenderer() { @@ -84,12 +88,28 @@ func (m *RenderSystem) createRenderer() { return } - config, found := m.configMap.GetGameConfig(configs[0]) + config, found := m.GetGameConfig(configs[0]) if !found { return } - renderer, err := d2render.CreateRenderer() + // d2render.CreateRenderer should use a GameConfigComponent instead ... + oldStyleConfig := &d2config.Configuration{ + MpqLoadOrder: config.MpqLoadOrder, + Language: config.Language, + MpqPath: config.MpqPath, + TicksPerSecond: config.TicksPerSecond, + FpsCap: config.FpsCap, + SfxVolume: config.SfxVolume, + BgmVolume: config.BgmVolume, + FullScreen: config.FullScreen, + RunInBackground: config.RunInBackground, + VsyncEnabled: config.VsyncEnabled, + Backend: config.Backend, + LogLevel: config.LogLevel, + } + + renderer, err := d2render.CreateRenderer(oldStyleConfig) if err != nil { panic(err) } @@ -105,23 +125,27 @@ func (m *RenderSystem) createRenderer() { m.lastUpdate = time.Now() - _ = m.renderer.Run(m.wrapWorldUpdate, 800, 600, gameTitle) + _ = m.renderer.Run(m.render, m.updateWorld, 800, 600, gameTitle) } -func (m *RenderSystem) render(id akara.EID) { - vp, found := m.viewportMap.GetViewPort(id) - if !found { - return +func (m *RenderSystem) render(screen d2interface.Surface) error { + m.screenSurface = screen + + for _, id := range m.viewports.GetEntities() { + vp, found := m.GetViewPort(id) + if !found { + return errors.New("viewport not found") + } + + if m.screenSurface != nil { + m.screenSurface.Render(vp.Surface) + } } - if m.screenSurface != nil { - _ = m.screenSurface.Render(vp.Surface) - } + return nil } -func (m *RenderSystem) wrapWorldUpdate(s d2interface.Surface) error { - m.screenSurface = s - +func (m *RenderSystem) updateWorld() error { currentTime := time.Now() elapsed := currentTime.Sub(m.lastUpdate) diff --git a/d2core/d2systems/timescale.go b/d2core/d2systems/timescale.go index ba40a9e1..e41d1d15 100644 --- a/d2core/d2systems/timescale.go +++ b/d2core/d2systems/timescale.go @@ -1,6 +1,7 @@ package d2systems import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "time" "github.com/gravestench/akara" @@ -10,12 +11,19 @@ const ( defaultScale float64 = 1 ) +const ( + logPrefixTimeScaleSystem = "Time Scale" +) + // NewTimeScaleSystem creates a timescale system func NewTimeScaleSystem() *TimeScaleSystem { m := &TimeScaleSystem{ BaseSystem: &akara.BaseSystem{}, + Logger: d2util.NewLogger(), } + m.SetPrefix(logPrefixTimeScaleSystem) + return m } @@ -27,20 +35,28 @@ var _ akara.System = &TimeScaleSystem{} // up the game time without affecting the render rate. type TimeScaleSystem struct { *akara.BaseSystem + *d2util.Logger scale float64 + lastScale float64 } // Init will initialize the TimeScale system func (t *TimeScaleSystem) Init(world *akara.World) { t.World = world + + t.Info("initializing ...") + t.scale = defaultScale } // Process scales the worlds time delta for this frame func (t *TimeScaleSystem) Process() { - if !t.Active() { + if !t.Active() || t.scale == t.lastScale{ return } + t.Infof("setting time scale to %.1f", t.scale) + t.lastScale = t.scale + t.World.TimeDelta *= time.Duration(t.scale) } diff --git a/go.mod b/go.mod index 85c8fc93..54076540 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect - github.com/davecgh/go-spew v1.1.0 github.com/go-restruct/restruct v1.2.0-alpha github.com/google/uuid v1.1.2 github.com/gravestench/akara v0.0.0-20201014060234-a64208a7fd3c + github.com/hajimehoshi/ebiten v1.12.3 github.com/hajimehoshi/ebiten/v2 v2.0.0 github.com/pkg/errors v0.9.1 // indirect github.com/pkg/profile v1.5.0 diff --git a/go.sum b/go.sum index fc91a5d2..2c716320 100644 --- a/go.sum +++ b/go.sum @@ -15,11 +15,15 @@ github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSr github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk= github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gravestench/akara v0.0.0-20201014060234-a64208a7fd3c h1:WopE590cKxkcKXcOee4gPXHqtzwbarLClCaWNCdLqgI= github.com/gravestench/akara v0.0.0-20201014060234-a64208a7fd3c/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ= +github.com/hajimehoshi/bitmapfont v1.3.0/go.mod h1:/Qb7yVjHYNUV4JdqNkPs6BSZwLjKqkZOMIp6jZD0KgE= github.com/hajimehoshi/bitmapfont/v2 v2.1.0/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs= +github.com/hajimehoshi/ebiten v1.12.3 h1:/KYCLW5VvfMKOMb8TqjKFlDCQAibM+OiA+LUwjS8t0E= +github.com/hajimehoshi/ebiten v1.12.3/go.mod h1:9JW9BAz1+swszT0SXR8VUvNvDafs9VspevAcY+KPlV8= github.com/hajimehoshi/ebiten/v2 v2.0.0 h1:G8mhkKFtnDPPZ/ChaGWx4Bm0NusYEcafGCJ8QLxEaYs= github.com/hajimehoshi/ebiten/v2 v2.0.0/go.mod h1:hpZZQ/kk8DZqft7QsQ5hZLRQXHSZPdKnaa0tcJ3CZFE= github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= @@ -62,6 +66,7 @@ golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7/go.mod h1:1phAWC201xIgDyaFpm golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= @@ -86,6 +91,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201028215240-c5abc1b1d397 h1:YZ169h3kkKEXsueizzMwOT9AaiffbOa6oXSmUFJ4vxM= @@ -94,9 +100,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9 h1:KOkk4e2xd5OeCDJGwacvr75ICCbCsShrHiqPEdsA9hg= -golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201009162240-fcf82128ed91/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=