diff --git a/.gitignore b/.gitignore index af8a78ef..e7fe7474 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ **/*.pprof *.swp .*.swp -tags \ No newline at end of file +tags +heap.out +heap.pdf diff --git a/d2common/d2data/animation_data.go b/d2common/d2data/animation_data.go index d6387b53..59fe3e5a 100644 --- a/d2common/d2data/animation_data.go +++ b/d2common/d2data/animation_data.go @@ -29,13 +29,13 @@ func LoadAnimationData(rawData []byte) { for !streamReader.Eof() { dataCount := int(streamReader.GetInt32()) for i := 0; i < dataCount; i++ { - cofNameBytes, _ := streamReader.ReadBytes(8) + cofNameBytes := streamReader.ReadBytes(8) data := &AnimationDataRecord{ COFName: strings.ReplaceAll(string(cofNameBytes), string(0), ""), FramesPerDirection: int(streamReader.GetInt32()), AnimationSpeed: int(streamReader.GetInt32()), } - data.Flags, _ = streamReader.ReadBytes(144) + data.Flags = streamReader.ReadBytes(144) cofIndex := strings.ToLower(data.COFName) if _, found := AnimationData[cofIndex]; !found { AnimationData[cofIndex] = make([]*AnimationDataRecord, 0) diff --git a/d2common/d2data/d2datadict/object_types.go b/d2common/d2data/d2datadict/object_types.go index f69b50fa..6ab891e0 100644 --- a/d2common/d2data/d2datadict/object_types.go +++ b/d2common/d2data/d2datadict/object_types.go @@ -19,8 +19,8 @@ func LoadObjectTypes(objectTypeData []byte) { count := streamReader.GetInt32() ObjectTypes = make([]ObjectTypeRecord, count) for i := range ObjectTypes { - nameBytes, _ := streamReader.ReadBytes(32) - tokenBytes, _ := streamReader.ReadBytes(20) + nameBytes := streamReader.ReadBytes(32) + tokenBytes := streamReader.ReadBytes(20) ObjectTypes[i] = ObjectTypeRecord{ Name: strings.TrimSpace(strings.ReplaceAll(string(nameBytes), string(0), "")), Token: strings.TrimSpace(strings.ReplaceAll(string(tokenBytes), string(0), "")), diff --git a/d2common/d2data/d2video/binkdecoder.go b/d2common/d2data/d2video/binkdecoder.go index 20c19ef0..49d3494c 100644 --- a/d2common/d2data/d2video/binkdecoder.go +++ b/d2common/d2data/d2video/binkdecoder.go @@ -70,7 +70,7 @@ func (v *BinkDecoder) GetNextFrame() { func (v *BinkDecoder) loadHeaderInformation() { v.streamReader.SetPosition(0) - headerBytes, _ := v.streamReader.ReadBytes(3) + headerBytes := v.streamReader.ReadBytes(3) if string(headerBytes) != "BIK" { log.Fatal("Invalid header for bink video") } diff --git a/d2common/d2fileformats/d2cof/cof.go b/d2common/d2fileformats/d2cof/cof.go index 6af6ab62..05e3c5b3 100644 --- a/d2common/d2fileformats/d2cof/cof.go +++ b/d2common/d2fileformats/d2cof/cof.go @@ -37,19 +37,19 @@ func LoadCOF(fileData []byte) (*COF, error) { layer.Selectable = streamReader.GetByte() != 0 layer.Transparent = streamReader.GetByte() != 0 layer.DrawEffect = d2enum.DrawEffect(streamReader.GetByte()) - weaponClassStr, _ := streamReader.ReadBytes(4) + weaponClassStr := streamReader.ReadBytes(4) layer.WeaponClass = d2enum.WeaponClassFromString(strings.TrimSpace(strings.ReplaceAll(string(weaponClassStr), string(0), ""))) result.CofLayers[i] = layer result.CompositeLayers[layer.Type] = i } - animationFrameBytes, _ := streamReader.ReadBytes(result.FramesPerDirection) + animationFrameBytes := streamReader.ReadBytes(result.FramesPerDirection) result.AnimationFrames = make([]d2enum.AnimationFrame, result.FramesPerDirection) for i := range animationFrameBytes { result.AnimationFrames[i] = d2enum.AnimationFrame(animationFrameBytes[i]) } priorityLen := result.FramesPerDirection * result.NumberOfDirections * result.NumberOfLayers result.Priority = make([][][]d2enum.CompositeType, result.NumberOfDirections) - priorityBytes, _ := streamReader.ReadBytes(priorityLen) + priorityBytes := streamReader.ReadBytes(priorityLen) priorityIndex := 0 for direction := 0; direction < result.NumberOfDirections; direction++ { result.Priority[direction] = make([][]d2enum.CompositeType, result.FramesPerDirection) diff --git a/d2common/d2fileformats/d2dt1/dt1.go b/d2common/d2fileformats/d2dt1/dt1.go index f3070406..197b9653 100644 --- a/d2common/d2fileformats/d2dt1/dt1.go +++ b/d2common/d2fileformats/d2dt1/dt1.go @@ -74,7 +74,7 @@ func LoadDT1(fileData []byte) (*DT1, error) { } for blockIndex, block := range tile.Blocks { br.SetPosition(uint64(tile.blockHeaderPointer + block.FileOffset)) - encodedData, _ := br.ReadBytes(int(block.Length)) + encodedData := br.ReadBytes(int(block.Length)) tile.Blocks[blockIndex].EncodedData = encodedData } diff --git a/d2common/stream_reader.go b/d2common/stream_reader.go index 436670e3..7fef1481 100644 --- a/d2common/stream_reader.go +++ b/d2common/stream_reader.go @@ -103,12 +103,10 @@ func (v *StreamReader) ReadByte() (byte, error) { } // ReadBytes reads multiple bytes -func (v *StreamReader) ReadBytes(count int) ([]byte, error) { - result := make([]byte, count) - for i := 0; i < count; i++ { - result[i] = v.GetByte() - } - return result, nil +func (v *StreamReader) ReadBytes(count int) []byte { + result := v.data[v.position : v.position+uint64(count)] + v.position += uint64(count) + return result } func (v *StreamReader) SkipBytes(count int) { diff --git a/d2common/text_dictionary.go b/d2common/text_dictionary.go index be484a7e..a3c55a60 100644 --- a/d2common/text_dictionary.go +++ b/d2common/text_dictionary.go @@ -39,9 +39,7 @@ func LoadDictionary(dictionaryData []byte) { } br := CreateStreamReader(dictionaryData) // CRC - if _, err := br.ReadBytes(2); err != nil { - log.Fatal("Error reading CRC") - } + br.ReadBytes(2) numberOfElements := br.GetUInt16() hashTableSize := br.GetUInt32() // Version (always 0) @@ -74,7 +72,7 @@ func LoadDictionary(dictionaryData []byte) { continue } br.SetPosition(uint64(hashEntry.NameString)) - nameVal, _ := br.ReadBytes(int(hashEntry.NameLength - 1)) + nameVal := br.ReadBytes(int(hashEntry.NameLength - 1)) value := string(nameVal) br.SetPosition(uint64(hashEntry.IndexString)) key := "" diff --git a/d2core/d2map/act1_overworld.go b/d2core/d2map/act1_overworld.go index 6e4ee8a4..444b2900 100644 --- a/d2core/d2map/act1_overworld.go +++ b/d2core/d2map/act1_overworld.go @@ -63,5 +63,4 @@ func (m *MapEngine) GenerateAct1Overworld(cacheTiles bool) { } } - } diff --git a/d2core/d2map/region.go b/d2core/d2map/region.go index e9a7114a..5f962e1a 100644 --- a/d2core/d2map/region.go +++ b/d2core/d2map/region.go @@ -20,6 +20,8 @@ import ( "github.com/beefsack/go-astar" ) +var imageCacheRecords map[uint32]d2render.Surface + type PathTile struct { Walkable bool Up, Down, Left, Right, UpLeft, UpRight, DownLeft, DownRight *PathTile @@ -76,28 +78,31 @@ func (t *PathTile) PathEstimatedCost(to astar.Pather) float64 { } type MapRegion struct { - tileRect d2common.Rectangle - regionPath string - levelType d2datadict.LevelTypeRecord - levelPreset d2datadict.LevelPresetRecord - tiles []d2dt1.Tile - ds1 *d2ds1.DS1 - palette *d2dat.DATPalette - startX float64 - startY float64 - imageCacheRecords map[uint32]d2render.Surface - seed int64 - currentFrame int - lastFrameTime float64 - walkableArea [][]PathTile + tileRect d2common.Rectangle + regionPath string + levelType d2datadict.LevelTypeRecord + levelPreset d2datadict.LevelPresetRecord + tiles []d2dt1.Tile + ds1 *d2ds1.DS1 + palette *d2dat.DATPalette + startX float64 + startY float64 + seed int64 + currentFrame int + lastFrameTime float64 + walkableArea [][]PathTile +} + +// Invalidates the global region image cache. Call this when you are changing regions +func InvalidateImageCache() { + imageCacheRecords = nil } func loadRegion(seed int64, tileOffsetX, tileOffsetY int, levelType d2enum.RegionIdType, levelPreset int, fileIndex int, cacheTiles bool) (*MapRegion, []MapEntity) { region := &MapRegion{ - levelType: d2datadict.LevelTypes[levelType], - levelPreset: d2datadict.LevelPresets[levelPreset], - imageCacheRecords: map[uint32]d2render.Surface{}, - seed: seed, + levelType: d2datadict.LevelTypes[levelType], + levelPreset: d2datadict.LevelPresets[levelPreset], + seed: seed, } region.palette, _ = loadPaletteForAct(levelType) @@ -613,12 +618,15 @@ func (mr *MapRegion) generateTileCache() { func (mr *MapRegion) getImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte) d2render.Surface { lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex) - return mr.imageCacheRecords[lookupIndex] + return imageCacheRecords[lookupIndex] } func (mr *MapRegion) setImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte, image d2render.Surface) { lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex) - mr.imageCacheRecords[lookupIndex] = image + if imageCacheRecords == nil { + imageCacheRecords = make(map[uint32]d2render.Surface) + } + imageCacheRecords[lookupIndex] = image } func (mr *MapRegion) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, tileY int) { diff --git a/d2game/d2gamescreen/map_engine_testing.go b/d2game/d2gamescreen/map_engine_testing.go index f23dd88d..77a82b5c 100644 --- a/d2game/d2gamescreen/map_engine_testing.go +++ b/d2game/d2gamescreen/map_engine_testing.go @@ -105,6 +105,7 @@ func CreateMapEngineTest(currentRegion int, levelPreset int) *MapEngineTest { } func (met *MapEngineTest) LoadRegionByIndex(n int, levelPreset, fileIndex int) { + d2map.InvalidateImageCache() for _, spec := range regions { if spec.regionType == d2enum.RegionIdType(n) { met.regionSpec = spec diff --git a/d2networking/d2server/game_server.go b/d2networking/d2server/game_server.go index 271c6ffe..72095677 100644 --- a/d2networking/d2server/game_server.go +++ b/d2networking/d2server/game_server.go @@ -44,7 +44,7 @@ func Create(openNetworkServer bool) { } mapEngine := d2map.CreateMapEngine(singletonServer.seed) - mapEngine.GenerateAct1Overworld(true) + mapEngine.GenerateAct1Overworld(false) singletonServer.mapEngines = append(singletonServer.mapEngines, mapEngine) singletonServer.scriptEngine.AddFunction("getMapEngines", func(call otto.FunctionCall) otto.Value { diff --git a/main.go b/main.go index 016ba62d..502206af 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,9 @@ import ( "image/png" "log" "os" + "os/exec" "runtime" + "runtime/pprof" "strconv" "sync" @@ -120,6 +122,12 @@ func initialize() error { } d2term.BindLogger() + d2term.BindAction("dumpheap", "dumps the heap to heap.out", func() { + fileOut, _ := os.Create("heap.out") + pprof.WriteHeapProfile(fileOut) + fileOut.Close() + exec.Command("go", "tool", "pprof", "--pdf", "./OpenDiablo2", "./heap.out", ">", "./memprofile.pdf") + }) d2term.BindAction("fullscreen", "toggles fullscreen", func() { fullscreen := !d2render.IsFullScreen() d2render.SetFullScreen(fullscreen)