mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-02 14:46:28 -05:00
Merge pull request #1026 from gravestench/ecs
merge upstream master into ecs branch
This commit is contained in:
commit
1409166bf0
299
d2app/app.go
299
d2app/app.go
@ -15,20 +15,18 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||
|
||||
"github.com/pkg/profile"
|
||||
"golang.org/x/image/colornames"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||
ebiten2 "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio/ebiten"
|
||||
@ -260,7 +258,7 @@ func (a *App) LoadConfig() (*d2config.Configuration, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.SetPath(filepath.Join(configAsset.Source().Path(), configAsset.Path()))
|
||||
//config.SetPath(filepath.Join(configAsset.Source().Path(), configAsset.Path()))
|
||||
|
||||
a.Infof("loaded configuration file from %s", config.Path())
|
||||
|
||||
@ -270,8 +268,8 @@ func (a *App) LoadConfig() (*d2config.Configuration, error) {
|
||||
// Run executes the application and kicks off the entire game process
|
||||
func (a *App) Run() (err error) {
|
||||
// add our possible config directories
|
||||
_, _ = a.asset.AddSource(filepath.Dir(d2config.LocalConfigPath()))
|
||||
_, _ = a.asset.AddSource(filepath.Dir(d2config.DefaultConfigPath()))
|
||||
_ = a.asset.AddSource(filepath.Dir(d2config.LocalConfigPath()), types.AssetSourceFileSystem)
|
||||
_ = a.asset.AddSource(filepath.Dir(d2config.DefaultConfigPath()), types.AssetSourceFileSystem)
|
||||
|
||||
if a.config, err = a.LoadConfig(); err != nil {
|
||||
return err
|
||||
@ -322,192 +320,6 @@ func (a *App) Run() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) initialize() error {
|
||||
if err := a.initConfig(a.config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.initLanguage()
|
||||
|
||||
if err := a.initDataDictionaries(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.timeScale = 1.0
|
||||
a.lastTime = d2util.Now()
|
||||
a.lastScreenAdvance = a.lastTime
|
||||
|
||||
a.renderer.SetWindowIcon("d2logo.png")
|
||||
a.terminal.BindLogger()
|
||||
|
||||
terminalCommands := []struct {
|
||||
name string
|
||||
desc string
|
||||
args []string
|
||||
fn func(args []string) error
|
||||
}{
|
||||
{"dumpheap", "dumps the heap to pprof/heap.pprof", nil, a.dumpHeap},
|
||||
{"fullscreen", "toggles fullscreen", nil, a.toggleFullScreen},
|
||||
{"capframe", "captures a still frame", []string{"filename"}, a.setupCaptureFrame},
|
||||
{"capgifstart", "captures an animation (start)", []string{"filename"}, a.startAnimationCapture},
|
||||
{"capgifstop", "captures an animation (stop)", nil, a.stopAnimationCapture},
|
||||
{"vsync", "toggles vsync", nil, a.toggleVsync},
|
||||
{"fps", "toggle fps counter", nil, a.toggleFpsCounter},
|
||||
{"timescale", "set scalar for elapsed time", []string{"float"}, a.setTimeScale},
|
||||
{"quit", "exits the game", nil, a.quitGame},
|
||||
{"screen-gui", "enters the gui playground screen", nil, a.enterGuiPlayground},
|
||||
{"js", "eval JS scripts", []string{"code"}, a.evalJS},
|
||||
}
|
||||
|
||||
for _, cmd := range terminalCommands {
|
||||
if err := a.terminal.Bind(cmd.name, cmd.desc, cmd.args, cmd.fn); err != nil {
|
||||
a.Fatalf("failed to bind action %q: %v", cmd.name, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
gui, err := d2gui.CreateGuiManager(a.asset, *a.Options.LogLevel, a.inputManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.guiManager = gui
|
||||
|
||||
a.screen = d2screen.NewScreenManager(a.ui, *a.Options.LogLevel, a.guiManager)
|
||||
|
||||
a.audio.SetVolumes(a.config.BgmVolume, a.config.SfxVolume)
|
||||
|
||||
if err := a.loadStrings(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.ui.Initialize()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
fmtErrSourceNotFound = `file not found: %s
|
||||
|
||||
Please check your config file at %s
|
||||
|
||||
Also, verify that the MPQ files exist at %s
|
||||
|
||||
Capitalization in the file name matters.
|
||||
`
|
||||
)
|
||||
|
||||
func (a *App) initConfig(config *d2config.Configuration) error {
|
||||
a.config = config
|
||||
|
||||
for _, mpqName := range a.config.MpqLoadOrder {
|
||||
cleanDir := filepath.Clean(a.config.MpqPath)
|
||||
srcPath := filepath.Join(cleanDir, mpqName)
|
||||
|
||||
_, err := a.asset.AddSource(srcPath)
|
||||
if err != nil {
|
||||
// nolint:stylecheck // we want a multiline error message here..
|
||||
return fmt.Errorf(fmtErrSourceNotFound, srcPath, a.config.Path(), a.config.MpqPath)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) initLanguage() {
|
||||
a.language = a.asset.LoadLanguage(d2resource.LocalLanguage)
|
||||
a.asset.Loader.SetLanguage(&a.language)
|
||||
|
||||
a.charset = d2resource.GetFontCharset(a.language)
|
||||
a.asset.Loader.SetCharset(&a.charset)
|
||||
}
|
||||
|
||||
func (a *App) initDataDictionaries() error {
|
||||
dictPaths := []string{
|
||||
d2resource.LevelType, d2resource.LevelPreset, d2resource.LevelWarp,
|
||||
d2resource.ObjectType, d2resource.ObjectDetails, d2resource.Weapons,
|
||||
d2resource.Armor, d2resource.Misc, d2resource.Books, d2resource.ItemTypes,
|
||||
d2resource.UniqueItems, d2resource.Missiles, d2resource.SoundSettings,
|
||||
d2resource.MonStats, d2resource.MonStats2, d2resource.MonPreset,
|
||||
d2resource.MonProp, d2resource.MonType, d2resource.MonMode,
|
||||
d2resource.MagicPrefix, d2resource.MagicSuffix, d2resource.ItemStatCost,
|
||||
d2resource.ItemRatio, d2resource.StorePage, d2resource.Overlays,
|
||||
d2resource.CharStats, d2resource.Hireling, d2resource.Experience,
|
||||
d2resource.Gems, d2resource.QualityItems, d2resource.Runes,
|
||||
d2resource.DifficultyLevels, d2resource.AutoMap, d2resource.LevelDetails,
|
||||
d2resource.LevelMaze, d2resource.LevelSubstitutions, d2resource.CubeRecipes,
|
||||
d2resource.SuperUniques, d2resource.Inventory, d2resource.Skills,
|
||||
d2resource.SkillCalc, d2resource.MissileCalc, d2resource.Properties,
|
||||
d2resource.SkillDesc, d2resource.BodyLocations, d2resource.Sets,
|
||||
d2resource.SetItems, d2resource.AutoMagic, d2resource.TreasureClass,
|
||||
d2resource.TreasureClassEx, d2resource.States, d2resource.SoundEnvirons,
|
||||
d2resource.Shrines, d2resource.ElemType, d2resource.PlrMode,
|
||||
d2resource.PetType, d2resource.NPC, d2resource.MonsterUniqueModifier,
|
||||
d2resource.MonsterEquipment, d2resource.UniqueAppellation, d2resource.MonsterLevel,
|
||||
d2resource.MonsterSound, d2resource.MonsterSequence, d2resource.PlayerClass,
|
||||
d2resource.MonsterPlacement, d2resource.ObjectGroup, d2resource.CompCode,
|
||||
d2resource.MonsterAI, d2resource.RarePrefix, d2resource.RareSuffix,
|
||||
d2resource.Events, d2resource.Colors, d2resource.ArmorType,
|
||||
d2resource.WeaponClass, d2resource.PlayerType, d2resource.Composite,
|
||||
d2resource.HitClass, d2resource.UniquePrefix, d2resource.UniqueSuffix,
|
||||
d2resource.CubeModifier, d2resource.CubeType, d2resource.HirelingDescription,
|
||||
d2resource.LowQualityItems,
|
||||
}
|
||||
|
||||
a.Info("Initializing asset manager")
|
||||
|
||||
for _, path := range dictPaths {
|
||||
err := a.asset.LoadRecords(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := a.initAnimationData(d2resource.AnimationData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
fmtLoadAnimData = "loading animation data from: %s"
|
||||
)
|
||||
|
||||
func (a *App) initAnimationData(path string) error {
|
||||
animDataBytes, err := a.asset.LoadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.Debugf(fmtLoadAnimData, path)
|
||||
|
||||
animData := d2data.LoadAnimationData(animDataBytes)
|
||||
|
||||
a.Infof("Loaded %d animation data records", len(animData))
|
||||
|
||||
a.asset.Records.Animation.Data = animData
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) loadStrings() error {
|
||||
tablePaths := []string{
|
||||
d2resource.PatchStringTable,
|
||||
d2resource.ExpansionStringTable,
|
||||
d2resource.StringTable,
|
||||
}
|
||||
|
||||
for _, tablePath := range tablePaths {
|
||||
_, err := a.asset.LoadStringTable(tablePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) renderDebug(target d2interface.Surface) {
|
||||
if !a.showFPS {
|
||||
return
|
||||
@ -636,57 +448,6 @@ func (a *App) allocRate(totalAlloc uint64, fps float64) float64 {
|
||||
return deltaAllocPerFrame * fps / bytesToMegabyte
|
||||
}
|
||||
|
||||
func (a *App) dumpHeap([]string) error {
|
||||
if _, err := os.Stat("./pprof/"); os.IsNotExist(err) {
|
||||
if err := os.Mkdir("./pprof/", 0750); err != nil {
|
||||
a.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
fileOut, err := os.Create("./pprof/heap.pprof")
|
||||
if err != nil {
|
||||
a.Error(err.Error())
|
||||
}
|
||||
|
||||
if err := pprof.WriteHeapProfile(fileOut); err != nil {
|
||||
a.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err := fileOut.Close(); err != nil {
|
||||
a.Fatal(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) evalJS(args []string) error {
|
||||
val, err := a.scriptEngine.Eval(args[0])
|
||||
if err != nil {
|
||||
a.terminal.Errorf(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
a.Info("%s" + val)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) toggleFullScreen([]string) error {
|
||||
fullscreen := !a.renderer.IsFullScreen()
|
||||
a.renderer.SetFullScreen(fullscreen)
|
||||
a.terminal.Infof("fullscreen is now: %v", fullscreen)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) setupCaptureFrame(args []string) error {
|
||||
a.captureState = captureStateFrame
|
||||
a.capturePath = args[0]
|
||||
a.captureFrames = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) doCaptureFrame(target d2interface.Surface) error {
|
||||
fp, err := os.Create(a.capturePath)
|
||||
if err != nil {
|
||||
@ -769,58 +530,6 @@ func (a *App) convertFramesToGif() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) startAnimationCapture(args []string) error {
|
||||
a.captureState = captureStateGif
|
||||
a.capturePath = args[0]
|
||||
a.captureFrames = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) stopAnimationCapture([]string) error {
|
||||
a.captureState = captureStateNone
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) toggleVsync([]string) error {
|
||||
vsync := !a.renderer.GetVSyncEnabled()
|
||||
a.renderer.SetVSyncEnabled(vsync)
|
||||
a.terminal.Infof("vsync is now: %v", vsync)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) toggleFpsCounter([]string) error {
|
||||
a.showFPS = !a.showFPS
|
||||
a.terminal.Infof("fps counter is now: %v", a.showFPS)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) setTimeScale(args []string) error {
|
||||
timeScale, err := strconv.ParseFloat(args[0], 64)
|
||||
if err != nil || timeScale <= 0 {
|
||||
a.terminal.Errorf("invalid time scale value")
|
||||
return nil
|
||||
}
|
||||
|
||||
a.terminal.Infof("timescale changed from %f to %f", a.timeScale, timeScale)
|
||||
a.timeScale = timeScale
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) quitGame([]string) error {
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) enterGuiPlayground([]string) error {
|
||||
a.screen.SetNextScreen(d2gamescreen.CreateGuiTestMain(a.renderer, a.guiManager, *a.Options.LogLevel, a.asset))
|
||||
return nil
|
||||
}
|
||||
|
||||
func createZeroedRing(n int) *ring.Ring {
|
||||
r := ring.New(n)
|
||||
for i := 0; i < n; i++ {
|
||||
|
139
d2app/console_commands.go
Normal file
139
d2app/console_commands.go
Normal file
@ -0,0 +1,139 @@
|
||||
package d2app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2gamescreen"
|
||||
)
|
||||
|
||||
func (a *App) initTerminalCommands() {
|
||||
terminalCommands := []struct {
|
||||
name string
|
||||
desc string
|
||||
args []string
|
||||
fn func(args []string) error
|
||||
}{
|
||||
{"dumpheap", "dumps the heap to pprof/heap.pprof", nil, a.dumpHeap},
|
||||
{"fullscreen", "toggles fullscreen", nil, a.toggleFullScreen},
|
||||
{"capframe", "captures a still frame", []string{"filename"}, a.setupCaptureFrame},
|
||||
{"capgifstart", "captures an animation (start)", []string{"filename"}, a.startAnimationCapture},
|
||||
{"capgifstop", "captures an animation (stop)", nil, a.stopAnimationCapture},
|
||||
{"vsync", "toggles vsync", nil, a.toggleVsync},
|
||||
{"fps", "toggle fps counter", nil, a.toggleFpsCounter},
|
||||
{"timescale", "set scalar for elapsed time", []string{"float"}, a.setTimeScale},
|
||||
{"quit", "exits the game", nil, a.quitGame},
|
||||
{"screen-gui", "enters the gui playground screen", nil, a.enterGuiPlayground},
|
||||
{"js", "eval JS scripts", []string{"code"}, a.evalJS},
|
||||
}
|
||||
|
||||
for _, cmd := range terminalCommands {
|
||||
if err := a.terminal.Bind(cmd.name, cmd.desc, cmd.args, cmd.fn); err != nil {
|
||||
a.Fatalf("failed to bind action %q: %v", cmd.name, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) dumpHeap([]string) error {
|
||||
if _, err := os.Stat("./pprof/"); os.IsNotExist(err) {
|
||||
if err := os.Mkdir("./pprof/", 0750); err != nil {
|
||||
a.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
fileOut, err := os.Create("./pprof/heap.pprof")
|
||||
if err != nil {
|
||||
a.Error(err.Error())
|
||||
}
|
||||
|
||||
if err := pprof.WriteHeapProfile(fileOut); err != nil {
|
||||
a.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err := fileOut.Close(); err != nil {
|
||||
a.Fatal(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) evalJS(args []string) error {
|
||||
val, err := a.scriptEngine.Eval(args[0])
|
||||
if err != nil {
|
||||
a.terminal.Errorf(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
a.Info("%s" + val)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) toggleFullScreen([]string) error {
|
||||
fullscreen := !a.renderer.IsFullScreen()
|
||||
a.renderer.SetFullScreen(fullscreen)
|
||||
a.terminal.Infof("fullscreen is now: %v", fullscreen)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) setupCaptureFrame(args []string) error {
|
||||
a.captureState = captureStateFrame
|
||||
a.capturePath = args[0]
|
||||
a.captureFrames = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) startAnimationCapture(args []string) error {
|
||||
a.captureState = captureStateGif
|
||||
a.capturePath = args[0]
|
||||
a.captureFrames = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) stopAnimationCapture([]string) error {
|
||||
a.captureState = captureStateNone
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) toggleVsync([]string) error {
|
||||
vsync := !a.renderer.GetVSyncEnabled()
|
||||
a.renderer.SetVSyncEnabled(vsync)
|
||||
a.terminal.Infof("vsync is now: %v", vsync)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) toggleFpsCounter([]string) error {
|
||||
a.showFPS = !a.showFPS
|
||||
a.terminal.Infof("fps counter is now: %v", a.showFPS)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) setTimeScale(args []string) error {
|
||||
timeScale, err := strconv.ParseFloat(args[0], 64)
|
||||
if err != nil || timeScale <= 0 {
|
||||
a.terminal.Errorf("invalid time scale value")
|
||||
return nil
|
||||
}
|
||||
|
||||
a.terminal.Infof("timescale changed from %f to %f", a.timeScale, timeScale)
|
||||
a.timeScale = timeScale
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) quitGame([]string) error {
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) enterGuiPlayground([]string) error {
|
||||
a.screen.SetNextScreen(d2gamescreen.CreateGuiTestMain(a.renderer, a.guiManager, *a.Options.LogLevel, a.asset))
|
||||
return nil
|
||||
}
|
177
d2app/initialization.go
Normal file
177
d2app/initialization.go
Normal file
@ -0,0 +1,177 @@
|
||||
package d2app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
|
||||
)
|
||||
|
||||
func (a *App) initialize() error {
|
||||
if err := a.initConfig(a.config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.initLanguage()
|
||||
|
||||
if err := a.initDataDictionaries(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.timeScale = 1.0
|
||||
a.lastTime = d2util.Now()
|
||||
a.lastScreenAdvance = a.lastTime
|
||||
|
||||
a.renderer.SetWindowIcon("d2logo.png")
|
||||
a.terminal.BindLogger()
|
||||
a.initTerminalCommands()
|
||||
|
||||
gui, err := d2gui.CreateGuiManager(a.asset, *a.Options.LogLevel, a.inputManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.guiManager = gui
|
||||
|
||||
a.screen = d2screen.NewScreenManager(a.ui, *a.Options.LogLevel, a.guiManager)
|
||||
|
||||
a.audio.SetVolumes(a.config.BgmVolume, a.config.SfxVolume)
|
||||
|
||||
if err := a.loadStrings(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.ui.Initialize()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
fmtErrSourceNotFound = `file not found: %s
|
||||
|
||||
Please check your config file at %s
|
||||
|
||||
Also, verify that the MPQ files exist at %s
|
||||
|
||||
Capitalization in the file name matters.
|
||||
`
|
||||
)
|
||||
|
||||
func (a *App) initConfig(config *d2config.Configuration) error {
|
||||
a.config = config
|
||||
|
||||
for _, mpqName := range a.config.MpqLoadOrder {
|
||||
cleanDir := filepath.Clean(a.config.MpqPath)
|
||||
srcPath := filepath.Join(cleanDir, mpqName)
|
||||
|
||||
err := a.asset.AddSource(srcPath, types.AssetSourceMPQ)
|
||||
if err != nil {
|
||||
// nolint:stylecheck // we want a multiline error message here..
|
||||
return fmt.Errorf(fmtErrSourceNotFound, srcPath, a.config.Path(), a.config.MpqPath)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) initLanguage() {
|
||||
a.language = a.asset.LoadLanguage(d2resource.LocalLanguage)
|
||||
a.asset.Loader.SetLanguage(&a.language)
|
||||
|
||||
a.charset = d2resource.GetFontCharset(a.language)
|
||||
a.asset.Loader.SetCharset(&a.charset)
|
||||
}
|
||||
|
||||
func (a *App) initDataDictionaries() error {
|
||||
dictPaths := []string{
|
||||
d2resource.LevelType, d2resource.LevelPreset, d2resource.LevelWarp,
|
||||
d2resource.ObjectType, d2resource.ObjectDetails, d2resource.Weapons,
|
||||
d2resource.Armor, d2resource.Misc, d2resource.Books, d2resource.ItemTypes,
|
||||
d2resource.UniqueItems, d2resource.Missiles, d2resource.SoundSettings,
|
||||
d2resource.MonStats, d2resource.MonStats2, d2resource.MonPreset,
|
||||
d2resource.MonProp, d2resource.MonType, d2resource.MonMode,
|
||||
d2resource.MagicPrefix, d2resource.MagicSuffix, d2resource.ItemStatCost,
|
||||
d2resource.ItemRatio, d2resource.StorePage, d2resource.Overlays,
|
||||
d2resource.CharStats, d2resource.Hireling, d2resource.Experience,
|
||||
d2resource.Gems, d2resource.QualityItems, d2resource.Runes,
|
||||
d2resource.DifficultyLevels, d2resource.AutoMap, d2resource.LevelDetails,
|
||||
d2resource.LevelMaze, d2resource.LevelSubstitutions, d2resource.CubeRecipes,
|
||||
d2resource.SuperUniques, d2resource.Inventory, d2resource.Skills,
|
||||
d2resource.SkillCalc, d2resource.MissileCalc, d2resource.Properties,
|
||||
d2resource.SkillDesc, d2resource.BodyLocations, d2resource.Sets,
|
||||
d2resource.SetItems, d2resource.AutoMagic, d2resource.TreasureClass,
|
||||
d2resource.TreasureClassEx, d2resource.States, d2resource.SoundEnvirons,
|
||||
d2resource.Shrines, d2resource.ElemType, d2resource.PlrMode,
|
||||
d2resource.PetType, d2resource.NPC, d2resource.MonsterUniqueModifier,
|
||||
d2resource.MonsterEquipment, d2resource.UniqueAppellation, d2resource.MonsterLevel,
|
||||
d2resource.MonsterSound, d2resource.MonsterSequence, d2resource.PlayerClass,
|
||||
d2resource.MonsterPlacement, d2resource.ObjectGroup, d2resource.CompCode,
|
||||
d2resource.MonsterAI, d2resource.RarePrefix, d2resource.RareSuffix,
|
||||
d2resource.Events, d2resource.Colors, d2resource.ArmorType,
|
||||
d2resource.WeaponClass, d2resource.PlayerType, d2resource.Composite,
|
||||
d2resource.HitClass, d2resource.UniquePrefix, d2resource.UniqueSuffix,
|
||||
d2resource.CubeModifier, d2resource.CubeType, d2resource.HirelingDescription,
|
||||
d2resource.LowQualityItems,
|
||||
}
|
||||
|
||||
a.Info("Initializing asset manager")
|
||||
|
||||
for _, path := range dictPaths {
|
||||
err := a.asset.LoadRecords(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := a.initAnimationData(d2resource.AnimationData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
fmtLoadAnimData = "loading animation data from: %s"
|
||||
)
|
||||
|
||||
func (a *App) initAnimationData(path string) error {
|
||||
animDataBytes, err := a.asset.LoadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.Debugf(fmtLoadAnimData, path)
|
||||
|
||||
animData := d2data.LoadAnimationData(animDataBytes)
|
||||
|
||||
a.Infof("Loaded %d animation data records", len(animData))
|
||||
|
||||
a.asset.Records.Animation.Data = animData
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) loadStrings() error {
|
||||
tablePaths := []string{
|
||||
d2resource.PatchStringTable,
|
||||
d2resource.ExpansionStringTable,
|
||||
d2resource.StringTable,
|
||||
}
|
||||
|
||||
for _, tablePath := range tablePaths {
|
||||
_, err := a.asset.LoadStringTable(tablePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -90,7 +90,7 @@ func (v *Stream) Read(buffer []byte, offset, count uint32) (readTotal uint32, er
|
||||
toRead := count
|
||||
for toRead > 0 {
|
||||
if read, err = v.readInternal(buffer, offset, toRead); err != nil {
|
||||
return 0, err
|
||||
return readTotal, err
|
||||
}
|
||||
|
||||
if read == 0 {
|
||||
@ -128,7 +128,7 @@ func (v *Stream) readInternal(buffer []byte, offset, count uint32) (uint32, erro
|
||||
func (v *Stream) copy(buffer []byte, offset, pos, count uint32) (uint32, error) {
|
||||
bytesToCopy := d2math.Min(uint32(len(v.Data))-pos, count)
|
||||
if bytesToCopy <= 0 {
|
||||
return 0, nil
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
copy(buffer[offset:offset+bytesToCopy], v.Data[pos:pos+bytesToCopy])
|
||||
|
@ -2,14 +2,13 @@ package asset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Source is an abstraction for something that can load and list assets
|
||||
type Source interface {
|
||||
fmt.Stringer
|
||||
Type() types.SourceType
|
||||
Open(name string) (Asset, error)
|
||||
Open(name string) (io.ReadSeeker, error)
|
||||
Path() string
|
||||
Exists(subPath string) bool
|
||||
}
|
||||
|
12
d2common/d2loader/filesystem/loader_provider.go
Normal file
12
d2common/d2loader/filesystem/loader_provider.go
Normal file
@ -0,0 +1,12 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
||||
)
|
||||
|
||||
// OnAddSource is a shim method to allow loading of filesystem sources
|
||||
func OnAddSource(path string) (asset.Source, error) {
|
||||
return &Source{
|
||||
Root: path,
|
||||
}, nil
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@ -22,21 +23,14 @@ func (s *Source) Type() types.SourceType {
|
||||
}
|
||||
|
||||
// Open opens a file with the given sub-path within the Root dir of the file system source
|
||||
func (s *Source) Open(subPath string) (asset.Asset, error) {
|
||||
file, err := os.Open(s.fullPath(subPath))
|
||||
func (s *Source) Open(subPath string) (io.ReadSeeker, error) {
|
||||
return os.Open(s.fullPath(subPath))
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
a := &Asset{
|
||||
assetType: types.Ext2AssetType(filepath.Ext(subPath)),
|
||||
source: s,
|
||||
path: subPath,
|
||||
file: file,
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
// Exists returns true if the file exists
|
||||
func (s *Source) Exists(subPath string) bool {
|
||||
_, err := os.Stat(s.fullPath(subPath))
|
||||
return os.IsExist(err)
|
||||
}
|
||||
|
||||
func (s *Source) fullPath(subPath string) string {
|
||||
|
@ -2,24 +2,25 @@ package d2loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/mpq"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/filesystem"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/filesystem"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/mpq"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCacheBudget = 1024 * 1024 * 512
|
||||
defaultCacheEntryWeight = 1
|
||||
errFmtFileNotFound = "file not found: %s"
|
||||
defaultCacheBudget = 1024 * 1024 * 512
|
||||
errFmtFileNotFound = "file not found: %s"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -33,7 +34,12 @@ const (
|
||||
|
||||
// NewLoader creates a new loader
|
||||
func NewLoader(l d2util.LogLevel) (*Loader, error) {
|
||||
loader := &Loader{}
|
||||
loader := &Loader{
|
||||
LoaderProviders: make(map[types.SourceType]func(path string) (asset.Source, error), 2),
|
||||
}
|
||||
|
||||
loader.LoaderProviders[types.AssetSourceMPQ] = mpq.NewSource
|
||||
loader.LoaderProviders[types.AssetSourceFileSystem] = filesystem.OnAddSource
|
||||
|
||||
loader.Cache = d2cache.CreateCache(defaultCacheBudget)
|
||||
loader.Logger = d2util.NewLogger()
|
||||
@ -51,7 +57,8 @@ type Loader struct {
|
||||
charset *string
|
||||
d2interface.Cache
|
||||
*d2util.Logger
|
||||
Sources []asset.Source
|
||||
LoaderProviders map[types.SourceType]func(path string) (asset.Source, error)
|
||||
Sources []asset.Source
|
||||
}
|
||||
|
||||
// SetLanguage sets the language for loader
|
||||
@ -66,7 +73,7 @@ func (l *Loader) SetCharset(charset *string) {
|
||||
|
||||
// Load attempts to load an asset with the given sub-path. The sub-path is relative to the root
|
||||
// of each asset source root (regardless of the type of asset source)
|
||||
func (l *Loader) Load(subPath string) (asset.Asset, error) {
|
||||
func (l *Loader) Load(subPath string) (io.ReadSeeker, error) {
|
||||
subPath = filepath.Clean(subPath)
|
||||
|
||||
if l.language != nil {
|
||||
@ -77,16 +84,6 @@ func (l *Loader) Load(subPath string) (asset.Asset, error) {
|
||||
subPath = strings.ReplaceAll(subPath, tableToken, *language)
|
||||
}
|
||||
|
||||
// first, we check the cache for an existing entry
|
||||
if cached, found := l.Retrieve(subPath); found {
|
||||
l.Debug(fmt.Sprintf("Retrieved `%s` from cache", subPath))
|
||||
|
||||
a := cached.(asset.Asset)
|
||||
_, err := a.Seek(0, 0)
|
||||
|
||||
return a, err
|
||||
}
|
||||
|
||||
// if it isn't in the cache, we check if each source can open the file
|
||||
for idx := range l.Sources {
|
||||
source := l.Sources[idx]
|
||||
@ -99,9 +96,9 @@ func (l *Loader) Load(subPath string) (asset.Asset, error) {
|
||||
}
|
||||
|
||||
srcBase, _ := filepath.Abs(source.Path())
|
||||
l.Info(fmt.Sprintf("from %s, loading %s", srcBase, subPath))
|
||||
l.Info(fmt.Sprintf("Loaded %s -> %s", srcBase, subPath))
|
||||
|
||||
return loadedAsset, l.Insert(subPath, loadedAsset, defaultCacheEntryWeight)
|
||||
return loadedAsset, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(errFmtFileNotFound, subPath)
|
||||
@ -111,52 +108,45 @@ func (l *Loader) Load(subPath string) (asset.Asset, error) {
|
||||
// or a file on the host filesystem. In the case that it is a file, the file extension is used
|
||||
// to determine the type of asset source. In the case that the path points to a directory, a
|
||||
// FileSystemSource will be added.
|
||||
func (l *Loader) AddSource(path string) (asset.Source, error) {
|
||||
func (l *Loader) AddSource(path string, sourceType types.SourceType) error {
|
||||
if l.Sources == nil {
|
||||
l.Sources = make([]asset.Source, 0)
|
||||
}
|
||||
|
||||
cleanPath := filepath.Clean(path)
|
||||
|
||||
info, err := os.Lstat(cleanPath)
|
||||
source, err := l.LoaderProviders[sourceType](cleanPath)
|
||||
|
||||
if err != nil {
|
||||
l.Error(err.Error())
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
mode := info.Mode()
|
||||
l.Infof("Adding source: '%s'", cleanPath)
|
||||
l.Sources = append(l.Sources, source)
|
||||
|
||||
sourceType := types.AssetSourceUnknown
|
||||
|
||||
if mode.IsDir() {
|
||||
sourceType = types.AssetSourceFileSystem
|
||||
}
|
||||
|
||||
if mode.IsRegular() {
|
||||
sourceType = types.CheckSourceType(cleanPath)
|
||||
}
|
||||
|
||||
switch sourceType {
|
||||
case types.AssetSourceMPQ:
|
||||
source, err := mpq.NewSource(cleanPath)
|
||||
if err == nil {
|
||||
l.Info(fmt.Sprintf("adding MPQ source `%s`", cleanPath))
|
||||
l.Sources = append(l.Sources, source)
|
||||
|
||||
return source, nil
|
||||
}
|
||||
case types.AssetSourceFileSystem:
|
||||
source := &filesystem.Source{
|
||||
Root: cleanPath,
|
||||
}
|
||||
|
||||
l.Info(fmt.Sprintf("adding filesystem source `%s`", cleanPath))
|
||||
l.Sources = append(l.Sources, source)
|
||||
|
||||
return source, nil
|
||||
case types.AssetSourceUnknown:
|
||||
l.Warning(fmt.Sprintf("unknown asset source `%s`", cleanPath))
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown asset source `%s`", cleanPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Loader) Exists(subPath string) bool {
|
||||
subPath = filepath.Clean(subPath)
|
||||
|
||||
if l.language != nil {
|
||||
charset := l.charset
|
||||
language := l.language
|
||||
|
||||
subPath = strings.ReplaceAll(subPath, fontToken, *charset)
|
||||
subPath = strings.ReplaceAll(subPath, tableToken, *language)
|
||||
}
|
||||
|
||||
// if it isn't in the cache, we check if each source can open the file
|
||||
for idx := range l.Sources {
|
||||
source := l.Sources[idx]
|
||||
|
||||
// if the source can open the file, then we cache it and return it
|
||||
if source.Exists(subPath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -2,12 +2,13 @@ package d2loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -36,11 +37,11 @@ func TestLoader_NewLoader(t *testing.T) {
|
||||
func TestLoader_AddSource(t *testing.T) {
|
||||
loader, _ := NewLoader(d2util.LogLevelDefault)
|
||||
|
||||
sourceA, errA := loader.AddSource(sourcePathA)
|
||||
sourceB, errB := loader.AddSource(sourcePathB)
|
||||
sourceC, errC := loader.AddSource(sourcePathC)
|
||||
sourceD, errD := loader.AddSource(sourcePathD)
|
||||
sourceE, errE := loader.AddSource(badSourcePath)
|
||||
errA := loader.AddSource(sourcePathA, types.AssetSourceFileSystem)
|
||||
errB := loader.AddSource(sourcePathB, types.AssetSourceFileSystem)
|
||||
errC := loader.AddSource(sourcePathC, types.AssetSourceFileSystem)
|
||||
errD := loader.AddSource(sourcePathD, types.AssetSourceFileSystem)
|
||||
errE := loader.AddSource(badSourcePath, types.AssetSourceMPQ)
|
||||
|
||||
if errA != nil {
|
||||
t.Error(errA)
|
||||
@ -62,25 +63,6 @@ func TestLoader_AddSource(t *testing.T) {
|
||||
t.Error("expecting error on bad file path")
|
||||
}
|
||||
|
||||
if sourceA.String() != sourcePathA {
|
||||
t.Error("source path not the same as what we added")
|
||||
}
|
||||
|
||||
if sourceB.String() != sourcePathB {
|
||||
t.Error("source path not the same as what we added")
|
||||
}
|
||||
|
||||
if sourceC.String() != sourcePathC {
|
||||
t.Error("source path not the same as what we added")
|
||||
}
|
||||
|
||||
if sourceD.String() != sourcePathD {
|
||||
t.Error("source path not the same as what we added")
|
||||
}
|
||||
|
||||
if sourceE != nil {
|
||||
t.Error("source for bad path should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:gocyclo // this is just a test, not a big deal if we ignore linter here
|
||||
@ -88,25 +70,25 @@ func TestLoader_Load(t *testing.T) {
|
||||
loader, _ := NewLoader(d2util.LogLevelDefault)
|
||||
|
||||
// we expect files common to any source to come from here
|
||||
commonSource, err := loader.AddSource(sourcePathB)
|
||||
err := loader.AddSource(sourcePathB, types.AssetSourceFileSystem)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
_, err = loader.AddSource(sourcePathD)
|
||||
err = loader.AddSource(sourcePathD, types.AssetSourceMPQ)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
_, err = loader.AddSource(sourcePathA)
|
||||
err = loader.AddSource(sourcePathA, types.AssetSourceFileSystem)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
_, err = loader.AddSource(sourcePathC)
|
||||
err = loader.AddSource(sourcePathC, types.AssetSourceFileSystem)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
log.Print(err)
|
||||
@ -124,8 +106,6 @@ func TestLoader_Load(t *testing.T) {
|
||||
|
||||
if entryCommon == nil || errCommon != nil {
|
||||
t.Error("common entry should exist")
|
||||
} else if entryCommon.Source() != commonSource {
|
||||
t.Error("common entry should come from the first loader source")
|
||||
}
|
||||
|
||||
if errA != nil || errB != nil || errC != nil || errD != nil {
|
||||
@ -145,7 +125,7 @@ func TestLoader_Load(t *testing.T) {
|
||||
buffer := make([]byte, 1)
|
||||
|
||||
tests := []struct {
|
||||
entry asset.Asset
|
||||
entry io.ReadSeeker
|
||||
data string
|
||||
}{
|
||||
{entryCommon, "b"}, // sourcePathB is loaded first, we expect a "b"
|
||||
@ -172,8 +152,8 @@ func TestLoader_Load(t *testing.T) {
|
||||
got := string(result[0])
|
||||
|
||||
if got != expected {
|
||||
fmtStr := "unexpected data in file %s, loaded from source `%s`: expected `%s`, got `%s`"
|
||||
msg := fmt.Sprintf(fmtStr, entry.Path(), entry.Source(), expected, got)
|
||||
fmtStr := "unexpected data in file, expected %q, got %q"
|
||||
msg := fmt.Sprintf(fmtStr, expected, got)
|
||||
t.Error(msg)
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package mpq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
@ -42,12 +41,13 @@ func (a *Asset) Path() string {
|
||||
|
||||
// Read will read asset data into the given buffer
|
||||
func (a *Asset) Read(buf []byte) (n int, err error) {
|
||||
totalRead, err := a.stream.Read(buf)
|
||||
if totalRead == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
return totalRead, err
|
||||
return a.stream.Read(buf)
|
||||
//totalRead, err := a.stream.Read(buf)
|
||||
//if totalRead == 0 {
|
||||
// return 0, io.EOF
|
||||
//}
|
||||
//
|
||||
//return totalRead, err
|
||||
}
|
||||
|
||||
// Seek will seek the read position for the next read operation
|
||||
|
@ -1,12 +1,12 @@
|
||||
package mpq
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||
)
|
||||
|
||||
// static check that Source implements AssetSource
|
||||
@ -27,27 +27,16 @@ type Source struct {
|
||||
MPQ d2interface.Archive
|
||||
}
|
||||
|
||||
// Type returns the asset type, for MPQ's it always returns the MPQ asset source type
|
||||
func (v *Source) Type() types.SourceType {
|
||||
return types.AssetSourceMPQ
|
||||
// Open attempts to open a file within the MPQ archive
|
||||
func (v *Source) Open(name string) (a io.ReadSeeker, err error) {
|
||||
name = cleanName(name)
|
||||
return v.MPQ.ReadFileStream(name)
|
||||
}
|
||||
|
||||
// Open attempts to open a file within the MPQ archive
|
||||
func (v *Source) Open(name string) (a asset.Asset, err error) {
|
||||
name = cleanName(name)
|
||||
stream, err := v.MPQ.ReadFileStream(name)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a = &Asset{
|
||||
source: v,
|
||||
stream: stream,
|
||||
path: name,
|
||||
}
|
||||
|
||||
return a, nil
|
||||
// Exists returns true if the file exists
|
||||
func (v *Source) Exists(subPath string) bool {
|
||||
subPath = cleanName(subPath)
|
||||
return v.MPQ.Contains(subPath)
|
||||
}
|
||||
|
||||
// Path returns the path of the MPQ on the host filesystem
|
||||
|
@ -3,8 +3,17 @@ package d2asset
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
|
||||
@ -20,7 +29,6 @@ import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
|
||||
)
|
||||
|
||||
@ -33,6 +41,10 @@ const (
|
||||
fontBudget = 128
|
||||
paletteBudget = 64
|
||||
paletteTransformBudget = 64
|
||||
dt1Budget = 4096 * 2048 * 128
|
||||
ds1Budget = 4096 * 2048 * 128
|
||||
cofBudget = 4096 * 2048 * 128
|
||||
dccBudget = 4096 * 2048 * 128
|
||||
)
|
||||
|
||||
const (
|
||||
@ -53,6 +65,10 @@ type AssetManager struct {
|
||||
*d2util.Logger
|
||||
*d2loader.Loader
|
||||
tables []d2tbl.TextDictionary
|
||||
dt1s d2interface.Cache
|
||||
ds1s d2interface.Cache
|
||||
cofs d2interface.Cache
|
||||
dccs d2interface.Cache
|
||||
animations d2interface.Cache
|
||||
fonts d2interface.Cache
|
||||
palettes d2interface.Cache
|
||||
@ -69,7 +85,7 @@ func (am *AssetManager) SetLogLevel(level d2util.LogLevel) {
|
||||
}
|
||||
|
||||
// LoadAsset loads an asset
|
||||
func (am *AssetManager) LoadAsset(filePath string) (asset.Asset, error) {
|
||||
func (am *AssetManager) LoadAsset(filePath string) (io.ReadSeeker, error) {
|
||||
data, err := am.Loader.Load(filePath)
|
||||
if err != nil {
|
||||
errStr := fmt.Sprintf(fmtLoadAsset, filePath, err.Error())
|
||||
@ -81,19 +97,19 @@ func (am *AssetManager) LoadAsset(filePath string) (asset.Asset, error) {
|
||||
}
|
||||
|
||||
// LoadFileStream streams an MPQ file from a source file path
|
||||
func (am *AssetManager) LoadFileStream(filePath string) (d2interface.DataStream, error) {
|
||||
func (am *AssetManager) LoadFileStream(filePath string) (io.ReadSeeker, error) {
|
||||
am.Logger.Debugf("Loading FileStream: %s", filePath)
|
||||
return am.LoadAsset(filePath)
|
||||
}
|
||||
|
||||
// LoadFile loads an entire file from a source file path as a []byte
|
||||
func (am *AssetManager) LoadFile(filePath string) ([]byte, error) {
|
||||
func (am *AssetManager) LoadFile(filePath string) ([]byte, error) { // I DO NOT LIKE THIS! - Essial
|
||||
fileAsset, err := am.LoadAsset(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := fileAsset.Data()
|
||||
data, err := ioutil.ReadAll(fileAsset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -103,13 +119,11 @@ func (am *AssetManager) LoadFile(filePath string) ([]byte, error) {
|
||||
|
||||
// FileExists checks if a file exists on the underlying file system at the given file path.
|
||||
func (am *AssetManager) FileExists(filePath string) (bool, error) {
|
||||
filePath = filepath.Clean(filePath)
|
||||
|
||||
am.Logger.Debugf("Checking if file exists %s", filePath)
|
||||
|
||||
if loadedAsset, err := am.Loader.Load(filePath); err != nil || loadedAsset == nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return am.Loader.Exists(filePath), nil
|
||||
}
|
||||
|
||||
// LoadLanguage loads language from resource path
|
||||
@ -147,11 +161,6 @@ func (am *AssetManager) LoadAnimationWithEffect(animationPath, palettePath strin
|
||||
|
||||
am.Debugf(fmtLoadAnimation, animationPath, palettePath, effect)
|
||||
|
||||
animAsset, err := am.LoadAsset(animationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
palette, err := am.LoadPalette(palettePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -159,7 +168,7 @@ func (am *AssetManager) LoadAnimationWithEffect(animationPath, palettePath strin
|
||||
|
||||
var animation d2interface.Animation
|
||||
|
||||
switch animAsset.Type() {
|
||||
switch types.Ext2AssetType(filepath.Ext(animationPath)) {
|
||||
case types.AssetTypeDC6:
|
||||
animation, err = am.loadDC6(animationPath, palette, effect)
|
||||
if err != nil {
|
||||
@ -171,7 +180,7 @@ func (am *AssetManager) LoadAnimationWithEffect(animationPath, palettePath strin
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown Animation format for file: %s", animAsset.Path())
|
||||
return nil, fmt.Errorf("unknown Animation format for file: %s", animationPath)
|
||||
}
|
||||
|
||||
err = am.animations.Insert(cachePath, animation, defaultCacheEntryWeight)
|
||||
@ -237,12 +246,7 @@ func (am *AssetManager) LoadPalette(palettePath string) (d2interface.Palette, er
|
||||
return cached.(d2interface.Palette), nil
|
||||
}
|
||||
|
||||
paletteAsset, err := am.LoadAsset(palettePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if paletteAsset.Type() != types.AssetTypePalette {
|
||||
if types.Ext2AssetType(filepath.Ext(palettePath)) != types.AssetTypePalette {
|
||||
return nil, fmt.Errorf("not an instance of a palette: %s", palettePath)
|
||||
}
|
||||
|
||||
@ -390,12 +394,7 @@ func (am *AssetManager) loadDC6(path string,
|
||||
// loadDCC creates an Animation from d2dcc.DCC and d2dat.DATPalette
|
||||
func (am *AssetManager) loadDCC(path string,
|
||||
palette d2interface.Palette, effect d2enum.DrawEffect) (d2interface.Animation, error) {
|
||||
dccData, err := am.LoadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dcc, err := d2dcc.Load(dccData)
|
||||
dcc, err := am.LoadDCC(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -448,6 +447,10 @@ func (am *AssetManager) commandAssetSpam(term d2interface.Terminal) func([]strin
|
||||
am.fonts.SetVerbose(verbose)
|
||||
am.transforms.SetVerbose(verbose)
|
||||
am.animations.SetVerbose(verbose)
|
||||
am.dt1s.SetVerbose(verbose)
|
||||
am.ds1s.SetVerbose(verbose)
|
||||
am.dccs.SetVerbose(verbose)
|
||||
am.cofs.SetVerbose(verbose)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -474,6 +477,99 @@ func (am *AssetManager) commandAssetClear([]string) error {
|
||||
am.transforms.Clear()
|
||||
am.animations.Clear()
|
||||
am.fonts.Clear()
|
||||
am.dt1s.Clear()
|
||||
am.ds1s.Clear()
|
||||
am.dccs.Clear()
|
||||
am.cofs.Clear()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *AssetManager) LoadDT1(dt1Path string) (*d2dt1.DT1, error) {
|
||||
if dt1Value, found := am.dt1s.Retrieve(dt1Path); found {
|
||||
return dt1Value.(*d2dt1.DT1), nil
|
||||
}
|
||||
|
||||
fileData, err := am.LoadFile("/data/global/tiles/" + dt1Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not load /data/global/tiles/%s", dt1Path)
|
||||
}
|
||||
|
||||
dt1, err := d2dt1.LoadDT1(fileData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := am.dt1s.Insert(dt1Path, dt1, defaultCacheEntryWeight); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dt1, nil
|
||||
}
|
||||
|
||||
func (am *AssetManager) LoadDS1(ds1Path string) (*d2ds1.DS1, error) {
|
||||
if ds1Value, found := am.dt1s.Retrieve(ds1Path); found {
|
||||
return ds1Value.(*d2ds1.DS1), nil
|
||||
}
|
||||
|
||||
fileData, err := am.LoadFile("/data/global/tiles/" + ds1Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds1, err := d2ds1.LoadDS1(fileData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := am.dt1s.Insert(ds1Path, ds1, defaultCacheEntryWeight); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ds1, nil
|
||||
|
||||
}
|
||||
|
||||
func (am *AssetManager) LoadCOF(cofPath string) (*d2cof.COF, error) {
|
||||
if cofValue, found := am.cofs.Retrieve(cofPath); found {
|
||||
return cofValue.(*d2cof.COF), nil
|
||||
}
|
||||
|
||||
fileData, err := am.LoadFile(cofPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cof, err := d2cof.Load(fileData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := am.cofs.Insert(cofPath, cof, defaultCacheEntryWeight); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cof, nil
|
||||
}
|
||||
|
||||
func (am *AssetManager) LoadDCC(dccPath string) (*d2dcc.DCC, error) {
|
||||
if dccValue, found := am.dccs.Retrieve(dccPath); found {
|
||||
return dccValue.(*d2dcc.DCC), nil
|
||||
}
|
||||
|
||||
fileData, err := am.LoadFile(dccPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dcc, err := d2dcc.Load(fileData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := am.dccs.Insert(dccPath, dcc, defaultCacheEntryWeight); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dcc, nil
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof"
|
||||
@ -161,15 +162,26 @@ func (c *Composite) SetDirection(direction int) {
|
||||
return
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
c.direction = direction
|
||||
wg.Add(len(c.mode.layers))
|
||||
|
||||
for layerIdx := range c.mode.layers {
|
||||
layer := c.mode.layers[layerIdx]
|
||||
if layer != nil {
|
||||
if err := layer.SetDirection(c.direction); err != nil {
|
||||
fmt.Printf("failed to set direction of layer: %d, err: %v\n", layerIdx, err)
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
|
||||
layer := c.mode.layers[idx]
|
||||
|
||||
if layer != nil {
|
||||
if err := layer.SetDirection(c.direction); err != nil {
|
||||
fmt.Printf("failed to set direction of layer: %d, err: %v\n", idx, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}(layerIdx)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// GetDirection returns the current direction the composite is facing
|
||||
@ -248,7 +260,7 @@ func (c *Composite) createMode(animationMode animationMode, weaponClass string)
|
||||
return nil, fmt.Errorf("composite not found at path '%s': %v", cofPath, err)
|
||||
}
|
||||
|
||||
cof, err := c.loadCOF(cofPath)
|
||||
cof, err := c.LoadCOF(cofPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -306,13 +318,13 @@ func (c *Composite) loadCompositeLayer(layerKey, layerValue, animationMode, weap
|
||||
fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dc6", c.basePath, c.token, layerKey, c.token, layerKey, layerValue, animationMode, weaponClass),
|
||||
}
|
||||
|
||||
for _, animationPath := range animationPaths {
|
||||
exists, err := c.FileExists(animationPath)
|
||||
for idx := range animationPaths {
|
||||
exists, err := c.FileExists(animationPaths[idx])
|
||||
if !exists || err != nil {
|
||||
return nil, fmt.Errorf("animation path '%s' not found: %v", animationPath, err)
|
||||
return nil, fmt.Errorf("animation path '%s' not found: %v", animationPaths[idx], err)
|
||||
}
|
||||
|
||||
animation, err := c.LoadAnimationWithEffect(animationPath, palettePath, drawEffect)
|
||||
animation, err := c.LoadAnimationWithEffect(animationPaths[idx], palettePath, drawEffect)
|
||||
if err == nil {
|
||||
return animation, nil
|
||||
}
|
||||
@ -359,15 +371,6 @@ func (c *Composite) updateSize() {
|
||||
c.size.Height = biggestH
|
||||
}
|
||||
|
||||
func (c *Composite) loadCOF(cofPath string) (*d2cof.COF, error) {
|
||||
cofData, err := c.LoadFile(cofPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d2cof.Load(cofData)
|
||||
}
|
||||
|
||||
func baseString(baseType d2enum.ObjectType) string {
|
||||
switch baseType {
|
||||
case d2enum.ObjectTypePlayer:
|
||||
|
@ -32,6 +32,10 @@ func NewAssetManager(logLevel d2util.LogLevel) (*AssetManager, error) {
|
||||
fonts: d2cache.CreateCache(fontBudget),
|
||||
palettes: d2cache.CreateCache(paletteBudget),
|
||||
transforms: d2cache.CreateCache(paletteTransformBudget),
|
||||
dt1s: d2cache.CreateCache(dt1Budget),
|
||||
ds1s: d2cache.CreateCache(ds1Budget),
|
||||
cofs: d2cache.CreateCache(cofBudget),
|
||||
dccs: d2cache.CreateCache(dccBudget),
|
||||
Records: records,
|
||||
}
|
||||
|
||||
|
@ -137,6 +137,10 @@ func (eap *AudioProvider) createSoundEffect(sfx string, context *audio.Context,
|
||||
soundFile += sfx
|
||||
}
|
||||
|
||||
if fileExists, _ := eap.asset.FileExists(soundFile); !fileExists {
|
||||
soundFile = "data/global/music/" + sfx
|
||||
}
|
||||
|
||||
audioData, err := eap.asset.LoadFileStream(soundFile)
|
||||
|
||||
if err != nil {
|
||||
@ -153,6 +157,10 @@ func (eap *AudioProvider) createSoundEffect(sfx string, context *audio.Context,
|
||||
eap.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if d == nil {
|
||||
eap.Fatal("Decoded data is nil")
|
||||
}
|
||||
|
||||
var player *audio.Player
|
||||
|
||||
if loop {
|
||||
|
@ -3,13 +3,15 @@ package ebiten
|
||||
import (
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/audio"
|
||||
)
|
||||
|
||||
type panStream struct {
|
||||
io.ReadSeeker
|
||||
pan float64 // -1: left; 0: center; 1: right
|
||||
pan float64 // -1: left; 0: center; 1: right
|
||||
Lock sync.Mutex
|
||||
}
|
||||
|
||||
const (
|
||||
@ -24,6 +26,9 @@ func newPanStreamFromReader(src io.ReadSeeker) *panStream {
|
||||
}
|
||||
|
||||
func (s *panStream) Read(p []byte) (n int, err error) {
|
||||
s.Lock.Lock()
|
||||
defer s.Lock.Unlock()
|
||||
|
||||
n, err = s.ReadSeeker.Read(p)
|
||||
if err != nil {
|
||||
return
|
||||
@ -54,7 +59,9 @@ type SoundEffect struct {
|
||||
|
||||
// SetPan sets the audio pan, left is -1.0, center is 0.0, right is 1.0
|
||||
func (v *SoundEffect) SetPan(pan float64) {
|
||||
v.panStream.Lock.Lock()
|
||||
v.panStream.pan = pan
|
||||
v.panStream.Lock.Unlock()
|
||||
}
|
||||
|
||||
// SetVolume ets the volume
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
@ -97,13 +96,7 @@ func (m *MapEngine) addDT1(fileName string) {
|
||||
}
|
||||
}
|
||||
|
||||
fileData, err := m.asset.LoadFile("/data/global/tiles/" + fileName)
|
||||
if err != nil {
|
||||
m.Fatalf("Could not load /data/global/tiles/%s", fileName)
|
||||
return
|
||||
}
|
||||
|
||||
dt1, err := d2dt1.LoadDT1(fileData)
|
||||
dt1, err := m.asset.LoadDT1(fileName)
|
||||
if err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
@ -120,12 +113,7 @@ func (m *MapEngine) AddDS1(fileName string) {
|
||||
return
|
||||
}
|
||||
|
||||
fileData, err := m.asset.LoadFile("/data/global/tiles/" + fileName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ds1, err := d2ds1.LoadDS1(fileData)
|
||||
ds1, err := m.asset.LoadDS1(fileName)
|
||||
if err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
|
@ -88,11 +88,11 @@ func CreateMapRenderer(asset *d2asset.AssetManager, renderer d2interface.Rendere
|
||||
result.Camera.position = &startPosition
|
||||
result.viewport.SetCamera(&result.Camera)
|
||||
|
||||
if err := term.Bind("mapdebugvis", "set map debug visualization level", nil, result.commandMapDebugVis); err != nil {
|
||||
if err := term.Bind("mapdebugvis", "set map debug visualization level", []string{"level"}, result.commandMapDebugVis); err != nil {
|
||||
result.Errorf("could not bind the mapdebugvis action, err: %v", err)
|
||||
}
|
||||
|
||||
if err := term.Bind("entitydebugvis", "set entity debug visualization level", nil, result.commandEntityDebugVis); err != nil {
|
||||
if err := term.Bind("entitydebugvis", "set entity debug visualization level", []string{"level"}, result.commandEntityDebugVis); err != nil {
|
||||
result.Errorf("could not bind the entitydebugvis action, err: %v", err)
|
||||
}
|
||||
|
||||
@ -109,6 +109,10 @@ func (mr *MapRenderer) UnbindTerminalCommands(term d2interface.Terminal) error {
|
||||
}
|
||||
|
||||
func (mr *MapRenderer) commandMapDebugVis(args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("invalid argument supplied")
|
||||
}
|
||||
|
||||
level, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid argument supplied")
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||
)
|
||||
@ -54,12 +53,7 @@ func (f *StampFactory) LoadStamp(levelType d2enum.RegionIdType, levelPreset, fil
|
||||
continue
|
||||
}
|
||||
|
||||
fileData, err := f.asset.LoadFile("/data/global/tiles/" + levelTypeDt1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
dt1, err := d2dt1.LoadDT1(fileData)
|
||||
dt1, err := f.asset.LoadDT1(levelTypeDt1)
|
||||
if err != nil {
|
||||
f.Error(err.Error())
|
||||
return nil
|
||||
|
94
d2thread/mainthread.go
Normal file
94
d2thread/mainthread.go
Normal file
@ -0,0 +1,94 @@
|
||||
// Package d2thread is a package graciously taken from https://github.com/faiface/mainthread
|
||||
package d2thread
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// CallQueueCap is the capacity of the call queue. This means how many calls to CallNonBlock will not
|
||||
// block until some call finishes.
|
||||
//
|
||||
// The default value is 16 and should be good for 99% usecases.
|
||||
var (
|
||||
callQueue chan func() //nolint:gochecknoglobals
|
||||
)
|
||||
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
func checkRun() {
|
||||
if callQueue == nil {
|
||||
panic(errors.New("mainthread: did not call Run"))
|
||||
}
|
||||
}
|
||||
|
||||
// Run enables mainthread package functionality. To use mainthread package, put your main function
|
||||
// code into the run function (the argument to Run) and simply call Run from the real main function.
|
||||
//
|
||||
// Run returns when run (argument) function finishes.
|
||||
func Run(run func()) {
|
||||
var CallQueueCap = 16
|
||||
|
||||
callQueue = make(chan func(), CallQueueCap)
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
run()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case f := <-callQueue:
|
||||
f()
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CallNonBlock queues function f on the main thread and returns immediately. Does not wait until f
|
||||
// finishes.
|
||||
func CallNonBlock(f func()) {
|
||||
checkRun()
|
||||
callQueue <- f
|
||||
}
|
||||
|
||||
// Call queues function f on the main thread and blocks until the function f finishes.
|
||||
func Call(f func()) {
|
||||
checkRun()
|
||||
|
||||
done := make(chan struct{})
|
||||
callQueue <- func() {
|
||||
f()
|
||||
done <- struct{}{}
|
||||
}
|
||||
<-done
|
||||
}
|
||||
|
||||
// CallErr queues function f on the main thread and returns an error returned by f.
|
||||
func CallErr(f func() error) error {
|
||||
checkRun()
|
||||
|
||||
errChan := make(chan error)
|
||||
callQueue <- func() {
|
||||
errChan <- f()
|
||||
}
|
||||
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
// CallVal queues function f on the main thread and returns a value returned by f.
|
||||
func CallVal(f func() interface{}) interface{} {
|
||||
checkRun()
|
||||
|
||||
respChan := make(chan interface{})
|
||||
callQueue <- func() {
|
||||
respChan <- f()
|
||||
}
|
||||
|
||||
return <-respChan
|
||||
}
|
2
go.sum
2
go.sum
@ -4,6 +4,8 @@ github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0 h1:tDnuU0igiBiQFjs
|
||||
github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0/go.mod h1:h/5OEGj4G+fpYxluLjSMZbFY011ZxAntO98nCl8mrCs=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 h1:EBTWhcAX7rNQ80RLwLCpHZBBrJuzallFHnF+yMXo928=
|
||||
github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
|
Loading…
Reference in New Issue
Block a user