1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-20 22:25:24 +00:00

Added caching. Code cleanup.

This commit is contained in:
Tim Sarbin 2018-12-08 11:51:11 -05:00
parent 98bb55a8ea
commit 2bf53e351c
13 changed files with 209 additions and 92 deletions

View File

@ -18,7 +18,7 @@ namespace OpenDiablo2.Common.Interfaces
MPQFont GetMPQFont(string resourcePath);
MPQDS1 GetMPQDS1(string resourcePath, LevelPreset level, LevelDetail levelDetail, LevelType levelType);
MPQDT1 GetMPQDT1(string resourcePath);
Palette GetPalette(string paletteName);
Palette GetPalette(string paletteFile);
MPQCOF GetPlayerAnimation(eHero hero, eWeaponClass weaponClass, eMobMode mobMode);
MPQDCC GetPlayerDCC(MPQCOF.COFLayer cofLayer, eArmorType armorType, Palette palette);

View File

@ -0,0 +1,39 @@
/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C#
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
using System;
using System.Runtime.Caching;
namespace OpenDiablo2.Common.Interfaces
{
/// <summary>
/// Provides access to the cache system.
/// </summary>
public interface ICache
{
/// <summary>
/// Gets an item from the cache. If the item does not exist, the value factory will be executed to
/// generate the value.
/// </summary>
/// <typeparam name="T">The return type of the value</typeparam>
/// <param name="key">The name of the cache key, in the form of Type::X::Y::Z where
/// Type is the base type, and x/y/z are unique identifiers for the item.</param>
/// <param name="valueFactory">A function that returns the correct value if it does not already exist.</param>
/// <param name="cacheItemPolicy">Pass in a new policy to control how this item is handled. Typically you can leave this null.</param>
/// <returns>The item requested</returns>
T AddOrGetExisting<T>(string key, Func<T> valueFactory, CacheItemPolicy cacheItemPolicy = null);
}
}

View File

@ -1,17 +1,31 @@
using System;
/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C#
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OpenDiablo2.Common.Models
{
public sealed class MPQFont
{
public ImageSet FontImageSet;
public Dictionary<byte, Size> CharacterMetric = new Dictionary<byte, Size>();
public ImageSet FontImageSet { get; internal set; }
public Dictionary<char, Size> CharacterMetric { get; internal set; } = new Dictionary<char, Size>();
public static MPQFont LoadFromStream(Stream fontStream, Stream tableStream)
{
@ -31,7 +45,7 @@ namespace OpenDiablo2.Common.Models
br.ReadBytes(3);
var size = new Size(br.ReadByte(), br.ReadByte());
br.ReadBytes(3);
var charCode = br.ReadByte();
var charCode = (char)br.ReadByte();
result.CharacterMetric[charCode] = size;
br.ReadBytes(5);

View File

@ -45,6 +45,7 @@
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@ -80,6 +81,7 @@
<Compile Include="Enums\Mobs\eMobFlags.cs" />
<Compile Include="Enums\Mobs\eStatModifierType.cs" />
<Compile Include="Interfaces\Drawing\ICharacterRenderer.cs" />
<Compile Include="Interfaces\ICache.cs" />
<Compile Include="Interfaces\IItemManager.cs" />
<Compile Include="Extensions\MobManagerExtensions.cs" />
<Compile Include="Interfaces\IGameServer.cs" />

View File

@ -1,6 +1,21 @@
using Autofac;
/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C#
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
using Autofac;
using OpenDiablo2.Common.Interfaces;
using OpenDiablo2.Common.Interfaces.Drawing;
using OpenDiablo2.Common.Interfaces.Mobs;
using OpenDiablo2.Core.GameState_;
using OpenDiablo2.Core.Map_Engine;
@ -16,6 +31,7 @@ namespace OpenDiablo2.Core
{
log.Info("Configuring OpenDiablo2.Core service implementations.");
builder.RegisterType<Cache>().As<ICache>().SingleInstance();
builder.RegisterType<Button>().As<IButton>().InstancePerDependency();
builder.RegisterType<EngineDataManager>().As<IEngineDataManager>().SingleInstance();
builder.RegisterType<ItemManager>().As<IItemManager>().SingleInstance();

44
OpenDiablo2.Core/Cache.cs Normal file
View File

@ -0,0 +1,44 @@
/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C#
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
using System;
using System.Runtime.Caching;
using OpenDiablo2.Common.Interfaces;
namespace OpenDiablo2.Core
{
public sealed class Cache : ICache
{
private readonly static MemoryCache _cache = new MemoryCache("OpenDiablo2.Cache");
public Cache() { }
public T AddOrGetExisting<T>(string key, Func<T> valueFactory, CacheItemPolicy cacheItemPolicy = null)
{
var newValue = new Lazy<T>(valueFactory);
var oldValue = _cache.AddOrGetExisting(key, newValue, cacheItemPolicy ?? new CacheItemPolicy()) as Lazy<T>;
try
{
return (oldValue ?? newValue).Value;
}
catch
{
_cache.Remove(key);
throw;
}
}
}
}

View File

@ -44,6 +44,7 @@
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Runtime.Caching" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@ -56,6 +57,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AutofacModule.cs" />
<Compile Include="Cache.cs" />
<Compile Include="ItemManager.cs" />
<Compile Include="EngineDataManager.cs" />
<Compile Include="GameEngine.cs" />

View File

@ -1,8 +1,21 @@
using System;
/* OpenDiablo 2 - An open source re-implementation of Diablo 2 in C#
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenDiablo2.Common;
using OpenDiablo2.Common.Enums;
using OpenDiablo2.Common.Interfaces;
@ -12,19 +25,15 @@ namespace OpenDiablo2.Core
{
public sealed class ResourceManager : IResourceManager
{
private readonly ICache cache;
private readonly IMPQProvider mpqProvider;
private readonly IEngineDataManager engineDataManager;
private readonly Dictionary<string, ImageSet> ImageSets = new Dictionary<string, ImageSet>();
private readonly Dictionary<string, MPQFont> MPQFonts = new Dictionary<string, MPQFont>();
private readonly Dictionary<string, Palette> Palettes = new Dictionary<string, Palette>();
private readonly Dictionary<string, MPQDT1> DTs = new Dictionary<string, MPQDT1>();
private readonly Dictionary<string, MPQCOF> PlayerCOFs = new Dictionary<string, MPQCOF>();
public Dictionary<string, List<AnimationData>> Animations { get; private set; }
public Dictionary<string, List<AnimationData>> Animations { get; private set; } = new Dictionary<string, List<AnimationData>>();
public ResourceManager(IMPQProvider mpqProvider, IEngineDataManager engineDataManager)
public ResourceManager(ICache cache, IMPQProvider mpqProvider, IEngineDataManager engineDataManager)
{
this.cache = cache;
this.mpqProvider = mpqProvider;
this.engineDataManager = engineDataManager;
@ -32,70 +41,38 @@ namespace OpenDiablo2.Core
}
public ImageSet GetImageSet(string resourcePath)
{
if (!ImageSets.ContainsKey(resourcePath))
ImageSets[resourcePath] = ImageSet.LoadFromStream(mpqProvider.GetStream(resourcePath));
return ImageSets[resourcePath];
}
=> cache.AddOrGetExisting($"ImageSet::{resourcePath}", () => ImageSet.LoadFromStream(mpqProvider.GetStream(resourcePath)));
public MPQFont GetMPQFont(string resourcePath)
{
if (!MPQFonts.ContainsKey(resourcePath))
MPQFonts[resourcePath] = MPQFont.LoadFromStream(mpqProvider.GetStream($"{resourcePath}.DC6"), mpqProvider.GetStream($"{resourcePath}.tbl"));
return MPQFonts[resourcePath];
}
=> cache.AddOrGetExisting($"Font::{resourcePath}", () => MPQFont.LoadFromStream(mpqProvider.GetStream($"{resourcePath}.DC6"), mpqProvider.GetStream($"{resourcePath}.tbl")));
public MPQDS1 GetMPQDS1(string resourcePath, LevelPreset level, LevelDetail levelDetail, LevelType levelType)
{
var mapName = resourcePath.Replace("data\\global\\tiles\\", "").Replace("\\", "/");
return new MPQDS1(mpqProvider.GetStream(resourcePath), level, levelDetail, levelType, engineDataManager, this)
{
MapFile = resourcePath
};
}
=> cache.AddOrGetExisting($"DS1::{resourcePath}::{level}::{levelDetail}::{levelType}", ()
=> new MPQDS1(mpqProvider.GetStream(resourcePath), level, levelDetail, levelType, engineDataManager, this) { MapFile = resourcePath });
public Palette GetPalette(string paletteFile)
{
if (!Palettes.ContainsKey(paletteFile))
=> cache.AddOrGetExisting($"Palette::{paletteFile}", () =>
{
var paletteNameParts = paletteFile.Split('\\');
var paletteName = paletteNameParts[paletteNameParts.Count() - 2];
Palettes[paletteFile] = Palette.LoadFromStream(mpqProvider.GetStream(paletteFile), paletteName);
}
return Palettes[paletteFile];
}
return Palette.LoadFromStream(mpqProvider.GetStream(paletteFile), paletteName);
});
public MPQDT1 GetMPQDT1(string resourcePath)
{
if (!DTs.ContainsKey(resourcePath))
DTs[resourcePath] = new MPQDT1(mpqProvider.GetStream(resourcePath));
return DTs[resourcePath];
}
=> cache.AddOrGetExisting($"DT1::{resourcePath}", () => new MPQDT1(mpqProvider.GetStream(resourcePath)));
public MPQCOF GetPlayerAnimation(eHero hero, eWeaponClass weaponClass, eMobMode mobMode)
{
var key = $"{hero.ToToken()}{mobMode.ToToken()}{weaponClass.ToToken()}";
if (PlayerCOFs.ContainsKey(key))
return PlayerCOFs[key];
var path = $"{ResourcePaths.PlayerAnimationBase}\\{hero.ToToken()}\\COF\\{hero.ToToken()}{mobMode.ToToken()}{weaponClass.ToToken()}.cof";
var result = MPQCOF.Load(mpqProvider.GetStream(path), Animations, hero, weaponClass, mobMode);
PlayerCOFs[key] = result;
return result;
}
=> cache.AddOrGetExisting($"COF::{hero}::{weaponClass}::{mobMode}", () =>
{
var path = $"{ResourcePaths.PlayerAnimationBase}\\{hero.ToToken()}\\COF\\{hero.ToToken()}{mobMode.ToToken()}{weaponClass.ToToken()}.cof";
return MPQCOF.Load(mpqProvider.GetStream(path), Animations, hero, weaponClass, mobMode);
});
public MPQDCC GetPlayerDCC(MPQCOF.COFLayer cofLayer, eArmorType armorType, Palette palette)
{
// TODO: We need to cache this...
byte[] binaryData;
using (var stream = mpqProvider.GetStream(cofLayer.GetDCCPath(armorType)))
{
if (stream == null)
@ -107,5 +84,24 @@ namespace OpenDiablo2.Core
var result = new MPQDCC(binaryData, palette);
return result;
}
/*
=> cache.AddOrGetExisting($"DCC::{cofLayer}::{armorType}::{palette.Name}", () =>
{
byte[] binaryData;
using (var stream = mpqProvider.GetStream(cofLayer.GetDCCPath(armorType)))
{
if (stream == null)
return null;
binaryData = new byte[stream.Length];
stream.Read(binaryData, 0, (int)stream.Length);
}
var result = new MPQDCC(binaryData, palette);
return result;
});
/*
*/
}
}

View File

@ -59,7 +59,6 @@ namespace OpenDiablo2.SDL2_
private readonly IPaletteProvider paletteProvider;
private MPQCOF animationData;
private MPQDCC[] layerData;
static readonly byte[] directionConversion = new byte[] { 3, 15, 4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14 };
@ -138,19 +137,10 @@ namespace OpenDiablo2.SDL2_
throw new ApplicationException("Could not locate animation for the character!");
var palette = paletteProvider.PaletteTable["Units"];
var data = animationData.Layers
.Select(layer => resourceManager.GetPlayerDCC(layer, ArmorType, palette))
.ToArray();
log.Warn($"{data.Where(x => x == null).Count()} animation layers were not found!");
layerData = data.Where(x => x != null)
.ToArray();
CacheFrames();
CacheFrames(animationData.Layers.Select(layer => resourceManager.GetPlayerDCC(layer, ArmorType, palette)));
}
private unsafe void CacheFrames()
private unsafe void CacheFrames(IEnumerable<MPQDCC> layerData)
{
var cache = new DirectionCacheItem
{
@ -170,14 +160,26 @@ namespace OpenDiablo2.SDL2_
var maxX = Int32.MinValue;
var maxY = Int32.MinValue;
var layersIgnored = 0;
var layersToRender = new List<MPQDCC>();
foreach (var layer in layerData)
{
if (layer == null)
{
layersIgnored++;
continue;
}
layersToRender.Add(layer);
minX = Math.Min(minX, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Left);
minY = Math.Min(minY, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Top);
maxX = Math.Max(maxX, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Right);
maxY = Math.Max(maxY, layer.Directions[directionConversion[LocationDetails.MovementDirection]].Box.Bottom);
}
if (layersIgnored > 0)
log.Warn($"{layersIgnored} animation layer(s) were not found!");
var frameW = (maxX - minX) * 2; // Hack
var frameH = (maxY - minY) * 2;
@ -191,8 +193,11 @@ namespace OpenDiablo2.SDL2_
SDL.SDL_LockTexture(texture, IntPtr.Zero, out IntPtr pixels, out int pitch);
UInt32* data = (UInt32*)pixels;
foreach (var layer in layerData)
foreach (var layer in layersToRender)
{
if (layer == null)
continue;
var direction = layer.Directions[directionConversion[LocationDetails.MovementDirection]];
var frame = direction.Frames[frameIndex];
@ -212,8 +217,6 @@ namespace OpenDiablo2.SDL2_
continue;
var color = palette.Colors[paletteIndex];
var relativeX = (frame.XOffset - minX);
var relativeY = (frame.YOffset - minY);
var offsetX = x + cell.XOffset + (frame.Box.X - minX);
var offsetY = y + cell.YOffset + (frame.Box.Y - minY);
@ -228,6 +231,7 @@ namespace OpenDiablo2.SDL2_
SDL.SDL_UnlockTexture(texture);
SDL.SDL_SetTextureBlendMode(texture, SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND);
// TODO: Temporary code!
cache.SpriteTexture[frameIndex] = texture;
cache.SpriteRect[frameIndex] = new SDL.SDL_Rect { x = minX, y = minY, w = frameW, h = frameH };

View File

@ -48,7 +48,7 @@ namespace OpenDiablo2.SDL2_
{
int w = 0;
int h = 0;
foreach(byte ch in text)
foreach(var ch in text)
{
w += font.CharacterMetric[ch].Width;
h = Math.Max(h, font.CharacterMetric[ch].Height);

View File

@ -76,7 +76,7 @@ namespace OpenDiablo2.SDL2_
int h = 0;
foreach (var ch in text)
{
var metric = font.font.CharacterMetric[(byte)ch];
var metric = font.font.CharacterMetric[ch];
w += metric.Width;
h = Math.Max(Math.Max(h, metric.Height), font.sprite.FrameSize.Height);
}
@ -93,12 +93,12 @@ namespace OpenDiablo2.SDL2_
var height = font.sprite.FrameSize.Height;
for (int idx = 0; idx < text.Length; idx++)
{
width += font.font.CharacterMetric[(byte)text[idx]].Width;
width += font.font.CharacterMetric[text[idx]].Width;
if (width >= MaxWidth)
{
idx = lastWordIndex;
height += font.font.CharacterMetric[(byte)'|'].Height + 6;
height += font.font.CharacterMetric['|'].Height + 6;
width = 0;
continue;
}
@ -146,7 +146,7 @@ namespace OpenDiablo2.SDL2_
foreach (var ch in text)
{
WriteCharacter(cx, cy, (byte)ch);
cx += font.font.CharacterMetric[(byte)ch].Width;
cx += font.font.CharacterMetric[ch].Width;
}
}
else
@ -158,7 +158,7 @@ namespace OpenDiablo2.SDL2_
var lastStartX = 0;
for (int idx = 0; idx < text.Length; idx++)
{
width += font.font.CharacterMetric[(byte)text[idx]].Width;
width += font.font.CharacterMetric[text[idx]].Width;
if (width >= MaxWidth)
{
@ -180,7 +180,7 @@ namespace OpenDiablo2.SDL2_
var y = 0;
foreach(var line in linesToRender)
{
var lineWidth = (line.Sum(c => font.font.CharacterMetric[(byte)c].Width));
var lineWidth = (line.Sum(c => font.font.CharacterMetric[c].Width));
var x = 0;
if (Alignment == eTextAlign.Centered)
@ -191,10 +191,10 @@ namespace OpenDiablo2.SDL2_
foreach (var ch in line)
{
WriteCharacter(x, y, (byte)ch);
x += font.font.CharacterMetric[(byte)ch].Width;
x += font.font.CharacterMetric[ch].Width;
}
y += font.font.CharacterMetric[(byte)'|'].Height + 6;
y += font.font.CharacterMetric['|'].Height + 6;
}
}
SDL.SDL_SetRenderTarget(renderer, IntPtr.Zero);

View File

@ -5,7 +5,7 @@
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{2B0CF1AC-06DD-4322-AE8B-FF8A8C70A3CD}</ProjectGuid>
<OutputType>WinExe</OutputType>
<OutputType>Exe</OutputType>
<RootNamespace>OpenDiablo2</RootNamespace>
<AssemblyName>OpenDiablo2</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>

View File

@ -7,7 +7,7 @@
</root>
<appender name="console" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %level %logger - %message%newline" />
<conversionPattern value="%level %type{1}.%method - %message%newline" />
</layout>
</appender>
<appender name="file" type="log4net.Appender.RollingFileAppender">