From 5ac03d6f492bfcc2a6958d02cee207b7a2a01011 Mon Sep 17 00:00:00 2001 From: gravestench Date: Tue, 3 Nov 2020 12:54:15 +0000 Subject: [PATCH] refactored game bootstrap, removed `d2config.Config` singleton (#899) * Remove d2config.Config singleton * refactored config file bootstrap * `d2loader.Loader` adds the config directories during init * `d2asset.AssetManager` loads the config file during init * mpq verification logic removed from d2config; this is done by d2loader * added `errorMessage` to `d2app.App` for setting the error message for the error screen. * fixed loader test --- d2app/app.go | 70 +++++------ d2common/d2loader/loader.go | 45 ++----- d2common/d2loader/loader_test.go | 11 +- d2common/d2util/logger.go | 12 +- d2core/d2asset/asset_manager.go | 117 +++++++++++++++--- d2core/d2asset/d2asset.go | 29 ++--- .../d2audio/ebiten/ebiten_audio_provider.go | 4 +- d2core/d2config/d2config.go | 93 +++----------- d2core/d2config/default_directories.go | 28 +++++ d2core/d2config/defaults.go | 4 +- d2core/d2render/ebiten/ebiten_renderer.go | 6 +- 11 files changed, 224 insertions(+), 195 deletions(-) create mode 100644 d2core/d2config/default_directories.go diff --git a/d2app/app.go b/d2app/app.go index a2551b0d..b8dd5c0d 100644 --- a/d2app/app.go +++ b/d2app/app.go @@ -73,6 +73,8 @@ type App struct { ui *d2ui.UIManager tAllocSamples *ring.Ring guiManager *d2gui.GuiManager + config *d2config.Configuration + errorMessage error *Options } @@ -99,12 +101,20 @@ const ( // Create creates a new instance of the application func Create(gitBranch, gitCommit string) *App { + assetManager, assetError := d2asset.NewAssetManager() + + // we can throw away the error here because by this time it's already been loaded + config, _ := assetManager.LoadConfig() + return &App{ gitBranch: gitBranch, gitCommit: gitCommit, + asset: assetManager, + config: config, Options: &Options{ Server: &d2networking.ServerOptions{}, }, + errorMessage: assetError, } } @@ -113,20 +123,6 @@ func updateNOOP() error { } func (a *App) startDedicatedServer() error { - // hack, for now we need to create the asset manager here - // Attempt to load the configuration file - err := d2config.Load() - if err != nil { - return err - } - - asset, err := d2asset.NewAssetManager(d2config.Config, *a.Options.LogLevel) - if err != nil { - return err - } - - a.asset = asset - min, max := d2networking.ServerMinPlayers, d2networking.ServerMaxPlayersDefault maxPlayers := d2math.ClampInt(*a.Options.Server.MaxPlayers, min, max) @@ -154,35 +150,27 @@ func (a *App) startDedicatedServer() error { } func (a *App) loadEngine() error { - // Attempt to load the configuration file - configError := d2config.Load() - // Create our renderer - renderer, err := ebiten.CreateRenderer() + renderer, err := ebiten.CreateRenderer(a.config) if err != nil { return err } a.renderer = renderer - // If we failed to load our config, lets show the boot panic screen - if configError != nil { - return configError + if a.errorMessage != nil { + return a.renderer.Run(a.updateInitError, updateNOOP, 800, 600, "OpenDiablo2") } // if the log level was specified at the command line, use it logLevel := *a.Options.LogLevel if logLevel == d2util.LogLevelUnspecified { - logLevel = d2config.Config.LogLevel + logLevel = a.config.LogLevel } - // Create the asset manager - asset, err := d2asset.NewAssetManager(d2config.Config, logLevel) - if err != nil { - return err - } + a.asset.SetLogLevel(logLevel) - audio := ebiten2.CreateAudio(asset) + audio := ebiten2.CreateAudio(a.asset) inputManager := d2input.NewInputManager() @@ -191,21 +179,20 @@ func (a *App) loadEngine() error { return err } - err = asset.BindTerminalCommands(term) + err = a.asset.BindTerminalCommands(term) if err != nil { return err } scriptEngine := d2script.CreateScriptEngine() - uiManager := d2ui.NewUIManager(asset, renderer, inputManager, audio) + uiManager := d2ui.NewUIManager(a.asset, renderer, inputManager, audio) a.inputManager = inputManager a.terminal = term a.scriptEngine = scriptEngine a.audio = audio a.ui = uiManager - a.asset = asset a.tAllocSamples = createZeroedRing(nSamplesTAlloc) if a.gitBranch == "" { @@ -273,6 +260,13 @@ func (a *App) Run() error { return fmt.Errorf(fmtVersion, a.gitBranch, a.gitCommit) } + logLevel := *a.Options.LogLevel + if logLevel == d2util.LogLevelUnspecified { + logLevel = a.config.LogLevel + } + + a.asset.SetLogLevel(logLevel) + // start profiler if argument was supplied if len(*a.Options.profiler) > 0 { profiler := enableProfiler(*a.Options.profiler) @@ -296,7 +290,11 @@ func (a *App) Run() error { windowTitle := fmt.Sprintf("OpenDiablo2 (%s)", a.gitBranch) // If we fail to initialize, we will show the error screen if err := a.initialize(); err != nil { - if gameErr := a.renderer.Run(updateInitError, updateNOOP, 800, 600, + if a.errorMessage == nil { + a.errorMessage = err // if there was an error during init, don't clobber it + } + + if gameErr := a.renderer.Run(a.updateInitError, updateNOOP, 800, 600, windowTitle); gameErr != nil { return gameErr } @@ -352,8 +350,7 @@ func (a *App) initialize() error { a.screen = d2screen.NewScreenManager(a.ui, a.guiManager) - config := d2config.Config - a.audio.SetVolumes(config.BgmVolume, config.SfxVolume) + a.audio.SetVolumes(a.config.BgmVolume, a.config.SfxVolume) if err := a.loadStrings(); err != nil { return err @@ -727,11 +724,10 @@ func enableProfiler(profileOption string) interface{ Stop() } { return nil } -func updateInitError(target d2interface.Surface) error { +func (a *App) updateInitError(target d2interface.Surface) error { target.Clear(colornames.Darkred) target.PushTranslation(errMsgPadding, errMsgPadding) - target.DrawTextf(`Could not find the MPQ files in the directory: - %s\nPlease put the files and re-run the game.`, d2config.Config.MpqPath) + target.DrawTextf(a.errorMessage.Error()) return nil } diff --git a/d2common/d2loader/loader.go b/d2common/d2loader/loader.go index 6643c080..00cc823c 100644 --- a/d2common/d2loader/loader.go +++ b/d2common/d2loader/loader.go @@ -1,7 +1,6 @@ package d2loader import ( - "errors" "fmt" "os" "path/filepath" @@ -35,10 +34,8 @@ const ( ) // NewLoader creates a new loader -func NewLoader(config *d2config.Configuration, l d2util.LogLevel) (*Loader, error) { - loader := &Loader{ - config: config, - } +func NewLoader(l d2util.LogLevel) (*Loader, error) { + loader := &Loader{} loader.Cache = d2cache.CreateCache(defaultCacheBudget) loader.Logger = d2util.NewLogger() @@ -46,9 +43,9 @@ func NewLoader(config *d2config.Configuration, l d2util.LogLevel) (*Loader, erro loader.Logger.SetPrefix(logPrefix) loader.Logger.SetLevel(l) - err := loader.initFromConfig() + loader.bootstrap() - return loader, err + return loader, nil } // Loader represents the manager that handles loading and caching assets with the asset Sources @@ -60,35 +57,9 @@ type Loader struct { Sources []asset.Source } -const ( - errConfigFileNotFound = "config file not found" - fmtErrSourceNotFound = `file not found: %s - -Please check your config file at %s - -Also, verify that the MPQ files exist at %s - -Capitalization matters! -` -) - -func (l *Loader) initFromConfig() error { - if l.config == nil { - return errors.New(errConfigFileNotFound) - } - - for _, mpqName := range l.config.MpqLoadOrder { - cleanDir := filepath.Clean(l.config.MpqPath) - srcPath := filepath.Join(cleanDir, mpqName) - - _, err := l.AddSource(srcPath) - if err != nil { - // nolint:stylecheck // we want a multiline error message here.. - return fmt.Errorf(fmtErrSourceNotFound, srcPath, l.config.Path(), l.config.MpqPath) - } - } - - return nil +func (l *Loader) bootstrap() { + _, _ = l.AddSource(filepath.Dir(d2config.LocalConfigPath())) + _, _ = l.AddSource(filepath.Dir(d2config.DefaultConfigPath())) } // Load attempts to load an asset with the given sub-path. The sub-path is relative to the root @@ -125,7 +96,7 @@ func (l *Loader) Load(subPath string) (asset.Asset, error) { continue } - srcBase := filepath.Base(source.Path()) + srcBase, _ := filepath.Abs(source.Path()) l.Info(fmt.Sprintf("from %s, loading %s", srcBase, subPath)) return loadedAsset, l.Insert(subPath, loadedAsset, defaultCacheEntryWeight) diff --git a/d2common/d2loader/loader_test.go b/d2common/d2loader/loader_test.go index b8663499..6efbe78b 100644 --- a/d2common/d2loader/loader_test.go +++ b/d2common/d2loader/loader_test.go @@ -26,7 +26,7 @@ const ( ) func TestLoader_NewLoader(t *testing.T) { - loader, _ := NewLoader(nil, d2util.LogLevelDefault) + loader, _ := NewLoader(d2util.LogLevelDefault) if loader.Cache == nil { t.Error("loader should not be nil") @@ -34,7 +34,7 @@ func TestLoader_NewLoader(t *testing.T) { } func TestLoader_AddSource(t *testing.T) { - loader, _ := NewLoader(nil, d2util.LogLevelDefault) + loader, _ := NewLoader(d2util.LogLevelDefault) sourceA, errA := loader.AddSource(sourcePathA) sourceB, errB := loader.AddSource(sourcePathB) @@ -85,9 +85,10 @@ func TestLoader_AddSource(t *testing.T) { // nolint:gocyclo // this is just a test, not a big deal if we ignore linter here func TestLoader_Load(t *testing.T) { - loader, _ := NewLoader(nil, d2util.LogLevelDefault) + loader, _ := NewLoader(d2util.LogLevelDefault) - _, err := loader.AddSource(sourcePathB) // we expect files common to any source to come from here + // we expect files common to any source to come from here + commonSource, err := loader.AddSource(sourcePathB) if err != nil { t.Fail() log.Print(err) @@ -123,7 +124,7 @@ func TestLoader_Load(t *testing.T) { if entryCommon == nil || errCommon != nil { t.Error("common entry should exist") - } else if entryCommon.Source() != loader.Sources[0] { + } else if entryCommon.Source() != commonSource { t.Error("common entry should come from the first loader source") } diff --git a/d2common/d2util/logger.go b/d2common/d2util/logger.go index 79672cd4..204991ed 100644 --- a/d2common/d2util/logger.go +++ b/d2common/d2util/logger.go @@ -37,10 +37,10 @@ const colorEscapeReset = "\033[0m" // Log format strings for log levels const ( fmtPrefix = "[%s]" - LogFmtDebug = "[DEBUG]" + colorEscapeReset + " %s" - LogFmtInfo = "[INFO]" + colorEscapeReset + " %s" - LogFmtWarning = "[WARNING]" + colorEscapeReset + " %s" - LogFmtError = "[ERROR]" + colorEscapeReset + " %s" + LogFmtDebug = "[DEBUG]" + colorEscapeReset + " %s\r\n" + LogFmtInfo = "[INFO]" + colorEscapeReset + " %s\r\n" + LogFmtWarning = "[WARNING]" + colorEscapeReset + " %s\r\n" + LogFmtError = "[ERROR]" + colorEscapeReset + " %s\r\n" ) // NewLogger creates a new logger with a default @@ -73,6 +73,10 @@ func (l *Logger) SetPrefix(s string) { // SetLevel sets the log level func (l *Logger) SetLevel(level LogLevel) { + if level == LogLevelUnspecified { + level = LogLevelDefault + } + l.level = level } diff --git a/d2core/d2asset/asset_manager.go b/d2core/d2asset/asset_manager.go index 65bda5cb..ce9c835a 100644 --- a/d2core/d2asset/asset_manager.go +++ b/d2core/d2asset/asset_manager.go @@ -1,8 +1,12 @@ package d2asset import ( + "encoding/json" "fmt" "image/color" + "path/filepath" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2config" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" @@ -38,8 +42,22 @@ const ( paletteTransformBudget = 64 ) +const ( + logPrefix = "Asset Manager" + fmtLoadAsset = "could not load file stream %s (%v)" + fmtLoadAnimation = "loading animation %s with palette %s, draw effect %d" + fmtLoadComposite = "loading composite: type %d, token %s, palette %s" + fmtLoadFont = "loading font: table %s, sprite %s, palette %s" + fmtLoadPalette = "loading palette %s" + fmtLoadStringTable = "loading string table: %s" + fmtLoadTransform = "loading palette transform: %s" + fmtLoadDict = "loading data dictionary: %s" + fmtLoadAnimData = "loading animation data from: %s" +) + // AssetManager loads files and game objects type AssetManager struct { + config *d2config.Configuration logger *d2util.Logger loader *d2loader.Loader tables d2interface.Cache @@ -51,14 +69,98 @@ type AssetManager struct { } func (am *AssetManager) init() error { - err := am.initDataDictionaries() + var err error + + config, err := am.LoadConfig() if err != nil { return err } + am.logger.SetLevel(config.LogLevel) + am.Records.Logger.SetLevel(config.LogLevel) + am.loader.Logger.SetLevel(config.LogLevel) + + err = am.initConfig(config) + if err != nil { + return err + } + + if err := am.initDataDictionaries(); err != nil { + return err + } + return nil } +func (am *AssetManager) initConfig(config *d2config.Configuration) error { + am.config = config + + for _, mpqName := range am.config.MpqLoadOrder { + cleanDir := filepath.Clean(am.config.MpqPath) + srcPath := filepath.Join(cleanDir, mpqName) + + _, err := am.loader.AddSource(srcPath) + if err != nil { + // nolint:stylecheck // we want a multiline error message here.. + return fmt.Errorf(fmtErrSourceNotFound, srcPath, am.config.Path(), am.config.MpqPath) + } + } + + return nil +} + +// SetLogLevel sets the log level for the asset manager, record manager, and file loader +func (am *AssetManager) SetLogLevel(level d2util.LogLevel) { + am.logger.SetLevel(level) + am.Records.Logger.SetLevel(level) + am.loader.Logger.SetLevel(level) +} + +// LoadConfig loads the OpenDiablo2 config file +func (am *AssetManager) LoadConfig() (*d2config.Configuration, error) { + // by now the, the loader has initialized and added our config dirs as sources... + configBaseName := filepath.Base(d2config.DefaultConfigPath()) + + configAsset, _ := am.LoadAsset(configBaseName) + + config := &d2config.Configuration{} + + // create the default if not found + if configAsset == nil { + config = d2config.DefaultConfig() + + fullPath := filepath.Join(config.Dir(), config.Base()) + config.SetPath(fullPath) + + am.logger.Infof("creating default configuration file at %s...", fullPath) + + saveErr := config.Save() + + return config, saveErr + } + + if err := json.NewDecoder(configAsset).Decode(config); err != nil { + return nil, err + } + + config.SetPath(filepath.Join(configAsset.Source().Path(), configAsset.Path())) + + am.logger.Infof("loaded configuration file from %s", config.Path()) + + return config, nil +} + +const ( + fmtErrSourceNotFound = `file not found: %s + +Please check your config file at %s + +Also, verify that the MPQ files exist at %s + +Capitalization matters! +` +) + func (am *AssetManager) initDataDictionaries() error { dictPaths := []string{ d2resource.LevelType, d2resource.LevelPreset, d2resource.LevelWarp, @@ -108,19 +210,6 @@ func (am *AssetManager) initDataDictionaries() error { return nil } -const ( - logPrefix = "Asset Manager" - fmtLoadAsset = "could not load file stream %s (%v)" - fmtLoadAnimation = "loading animation %s with palette %s, draw effect %d" - fmtLoadComposite = "loading composite: type %d, token %s, palette %s" - fmtLoadFont = "loading font: table %s, sprite %s, palette %s" - fmtLoadPalette = "loading palette %s" - fmtLoadStringTable = "loading string table: %s" - fmtLoadTransform = "loading palette transform: %s" - fmtLoadDict = "loading data dictionary: %s" - fmtLoadAnimData = "loading animation data from: %s" -) - // LoadAsset loads an asset func (am *AssetManager) LoadAsset(filePath string) (asset.Asset, error) { data, err := am.loader.Load(filePath) diff --git a/d2core/d2asset/d2asset.go b/d2core/d2asset/d2asset.go index a909a6bc..a84ec4e0 100644 --- a/d2core/d2asset/d2asset.go +++ b/d2core/d2asset/d2asset.go @@ -4,40 +4,35 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2config" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" ) // NewAssetManager creates and assigns all necessary dependencies for the AssetManager top-level functions to work correctly -func NewAssetManager(config *d2config.Configuration, l d2util.LogLevel) (*AssetManager, error) { - loader, err := d2loader.NewLoader(config, l) +func NewAssetManager() (*AssetManager, error) { + loader, err := d2loader.NewLoader(d2util.LogLevelDefault) if err != nil { return nil, err } - records, err := d2records.NewRecordManager(l) + records, err := d2records.NewRecordManager(d2util.LogLevelDebug) if err != nil { return nil, err } manager := &AssetManager{ - d2util.NewLogger(), - loader, - d2cache.CreateCache(tableBudget), - d2cache.CreateCache(animationBudget), - d2cache.CreateCache(fontBudget), - d2cache.CreateCache(paletteBudget), - d2cache.CreateCache(paletteTransformBudget), - records, + logger: d2util.NewLogger(), + loader: loader, + tables: d2cache.CreateCache(tableBudget), + animations: d2cache.CreateCache(animationBudget), + fonts: d2cache.CreateCache(fontBudget), + palettes: d2cache.CreateCache(paletteBudget), + transforms: d2cache.CreateCache(paletteTransformBudget), + Records: records, } manager.logger.SetPrefix(logPrefix) - manager.logger.SetLevel(l) err = manager.init() - if err != nil { - return nil, err - } - return manager, nil + return manager, err } diff --git a/d2core/d2audio/ebiten/ebiten_audio_provider.go b/d2core/d2audio/ebiten/ebiten_audio_provider.go index 0da14d44..58ed6b13 100644 --- a/d2core/d2audio/ebiten/ebiten_audio_provider.go +++ b/d2core/d2audio/ebiten/ebiten_audio_provider.go @@ -120,7 +120,7 @@ func (eap *AudioProvider) createSoundEffect(sfx string, context *audio.Context, loop bool) *SoundEffect { result := &SoundEffect{} - soundFile := "/data/global/sfx/" + soundFile := "data/global/sfx/" if _, exists := eap.asset.Records.Sound.Details[sfx]; exists { soundEntry := eap.asset.Records.Sound.Details[sfx] @@ -132,7 +132,7 @@ func (eap *AudioProvider) createSoundEffect(sfx string, context *audio.Context, audioData, err := eap.asset.LoadFileStream(soundFile) if err != nil { - audioData, err = eap.asset.LoadFileStream("/data/global/music/" + sfx) + audioData, err = eap.asset.LoadFileStream("data/global/music/" + sfx) } if err != nil { diff --git a/d2core/d2config/d2config.go b/d2core/d2config/d2config.go index 41fc3886..535b447f 100644 --- a/d2core/d2config/d2config.go +++ b/d2core/d2config/d2config.go @@ -2,16 +2,13 @@ package d2config import ( "encoding/json" - "log" "os" "path" + "path/filepath" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" ) -// Config holds the configuration from config.json -var Config *Configuration //nolint:gochecknoglobals // Currently global by design - // Configuration defines the configuration for the engine, loaded from config.json type Configuration struct { MpqLoadOrder []string @@ -29,72 +26,19 @@ type Configuration struct { path string } -// Load loads a configuration object from disk -func Load() error { - Config = new(Configuration) - - if Config.Load() != nil { - return Config.Save() - } - - return nil -} - -// Load loads a configuration object from disk -func (c *Configuration) Load() error { - configPaths := []string{ - defaultConfigPath(), - localConfigPath(), - } - - for _, configPath := range configPaths { - log.Printf("loading configuration file from %s...", configPath) - - if _, err := os.Stat(configPath); os.IsNotExist(err) { - continue - } - - configFile, err := os.Open(path.Clean(configPath)) - if err != nil { - return err - } - - if err := json.NewDecoder(configFile).Decode(&Config); err != nil { - return err - } - - if err := configFile.Close(); err != nil { - return err - } - - c.path = configPath - - return nil - } - - log.Println("failed to load configuration file, saving default configuration...") - - Config = defaultConfig() - - return Config.Save() -} - // Save saves the configuration object to disk func (c *Configuration) Save() error { - configPath := defaultConfigPath() - log.Printf("saving configuration file to %s...", configPath) - - configDir := path.Dir(configPath) + configDir := path.Dir(c.path) if err := os.MkdirAll(configDir, 0750); err != nil { return err } - configFile, err := os.Create(configPath) + configFile, err := os.Create(c.path) if err != nil { return err } - buf, err := json.MarshalIndent(Config, "", " ") + buf, err := json.MarshalIndent(c, "", " ") if err != nil { return err } @@ -106,23 +50,22 @@ func (c *Configuration) Save() error { return configFile.Close() } -// Path returns the path of the config file -func (c *Configuration) Path() string { - if c.path == "" { - c.path = defaultConfigPath() - } +// Dir returns the directory component of the path +func (c *Configuration) Dir() string { + return filepath.Dir(c.path) +} +// Base returns the base component of the path +func (c *Configuration) Base() string { + return filepath.Base(c.path) +} + +// Path returns the config file path +func (c *Configuration) Path() string { return c.path } -func defaultConfigPath() string { - if configDir, err := os.UserConfigDir(); err == nil { - return path.Join(configDir, "OpenDiablo2", "config.json") - } - - return localConfigPath() -} - -func localConfigPath() string { - return path.Join(path.Dir(os.Args[0]), "config.json") +// SetPath sets where the config file is saved to (a full path) +func (c *Configuration) SetPath(p string) { + c.path = p } diff --git a/d2core/d2config/default_directories.go b/d2core/d2config/default_directories.go new file mode 100644 index 00000000..0c09859a --- /dev/null +++ b/d2core/d2config/default_directories.go @@ -0,0 +1,28 @@ +package d2config + +import ( + "os" + "path" +) + +const ( + od2ConfigDirName = "OpenDiablo2" +) + +const ( + od2ConfigFileName = "config.json" +) + +// DefaultConfigPath returns the absolute path for the default config file location +func DefaultConfigPath() string { + if configDir, err := os.UserConfigDir(); err == nil { + return path.Join(configDir, od2ConfigDirName, od2ConfigFileName) + } + + return LocalConfigPath() +} + +// LocalConfigPath returns the absolute path to the directory of the OpenDiablo2 executable +func LocalConfigPath() string { + return path.Join(path.Dir(os.Args[0]), od2ConfigFileName) +} diff --git a/d2core/d2config/defaults.go b/d2core/d2config/defaults.go index 0d38976a..beb0e23f 100644 --- a/d2core/d2config/defaults.go +++ b/d2core/d2config/defaults.go @@ -8,7 +8,8 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" ) -func defaultConfig() *Configuration { +// DefaultConfig creates and returns a default configuration +func DefaultConfig() *Configuration { const ( defaultSfxVolume = 1.0 defaultBgmVolume = 0.3 @@ -38,6 +39,7 @@ func defaultConfig() *Configuration { "d2speech.mpq", }, LogLevel: d2util.LogLevelDefault, + path: DefaultConfigPath(), } switch runtime.GOOS { diff --git a/d2core/d2render/ebiten/ebiten_renderer.go b/d2core/d2render/ebiten/ebiten_renderer.go index 9fc07647..47948e79 100644 --- a/d2core/d2render/ebiten/ebiten_renderer.go +++ b/d2core/d2render/ebiten/ebiten_renderer.go @@ -66,11 +66,11 @@ func (r *Renderer) Layout(_, _ int) (width, height int) { } // CreateRenderer creates an ebiten renderer instance -func CreateRenderer() (*Renderer, error) { +func CreateRenderer(cfg *d2config.Configuration) (*Renderer, error) { result := &Renderer{} - if d2config.Config != nil { - config := d2config.Config + if cfg != nil { + config := cfg ebiten.SetCursorMode(ebiten.CursorModeHidden) ebiten.SetFullscreen(config.FullScreen)