diff --git a/Hypercube.Client/Audio/Event/AudioLibraryInitializedEvent.cs b/Hypercube.Client/Audio/Event/AudioLibraryInitializedEvent.cs new file mode 100644 index 0000000..10c5ca4 --- /dev/null +++ b/Hypercube.Client/Audio/Event/AudioLibraryInitializedEvent.cs @@ -0,0 +1,5 @@ +using Hypercube.Shared.EventBus.Events; + +namespace Hypercube.Client.Audio.Event; + +public readonly record struct AudioLibraryInitializedEvent : IEventArgs; \ No newline at end of file diff --git a/Hypercube.Client/Audio/Realisations/OpenAL/ContextExtension.cs b/Hypercube.Client/Audio/Realisation/OpenAL/ContextExtension.cs similarity index 68% rename from Hypercube.Client/Audio/Realisations/OpenAL/ContextExtension.cs rename to Hypercube.Client/Audio/Realisation/OpenAL/ContextExtension.cs index 5648e79..d88daa3 100644 --- a/Hypercube.Client/Audio/Realisations/OpenAL/ContextExtension.cs +++ b/Hypercube.Client/Audio/Realisation/OpenAL/ContextExtension.cs @@ -1,4 +1,4 @@ -namespace Hypercube.Client.Audio.Realisations.OpenAL; +namespace Hypercube.Client.Audio.Realisation.OpenAL; public static class ContextExtension { diff --git a/Hypercube.Client/Audio/Realisations/OpenAL/OpenALAudioManager.cs b/Hypercube.Client/Audio/Realisation/OpenAL/OpenALAudioManager.cs similarity index 97% rename from Hypercube.Client/Audio/Realisations/OpenAL/OpenALAudioManager.cs rename to Hypercube.Client/Audio/Realisation/OpenAL/OpenALAudioManager.cs index 399b8b2..6b592c3 100644 --- a/Hypercube.Client/Audio/Realisations/OpenAL/OpenALAudioManager.cs +++ b/Hypercube.Client/Audio/Realisation/OpenAL/OpenALAudioManager.cs @@ -1,4 +1,5 @@ using System.Collections.Frozen; +using Hypercube.Client.Audio.Event; using Hypercube.Client.Audio.Loading; using Hypercube.Client.Utilities.Helpers; using Hypercube.Shared.Dependency; @@ -9,7 +10,7 @@ using Hypercube.Shared.Runtimes.Event; using OpenTK.Audio.OpenAL; -namespace Hypercube.Client.Audio.Realisations.OpenAL; +namespace Hypercube.Client.Audio.Realisation.OpenAL; /// /// For some reason, on my Windows 11 machine there is no openal32.dll @@ -57,6 +58,9 @@ public void Initialize() return; CreateContext(); + + _logger.EngineInfo("Initialized"); + _eventBus.Raise(new AudioLibraryInitializedEvent()); } private AudioStream CreateAudio(ResourcePath path, AudioSettings settings) diff --git a/Hypercube.Client/Dependencies.cs b/Hypercube.Client/Dependencies.cs index ccca210..f7b81fd 100644 --- a/Hypercube.Client/Dependencies.cs +++ b/Hypercube.Client/Dependencies.cs @@ -1,18 +1,20 @@ using Hypercube.Client.Audio; using Hypercube.Client.Audio.Loading; -using Hypercube.Client.Audio.Realisations.OpenAL; +using Hypercube.Client.Audio.Realisation.OpenAL; using Hypercube.Client.Graphics.Drawing; using Hypercube.Client.Graphics.Rendering; using Hypercube.Client.Graphics.Texturing; using Hypercube.Client.Graphics.Viewports; using Hypercube.Client.Input.Handler; using Hypercube.Client.Input.Manager; +using Hypercube.Client.Resources.Caching; using Hypercube.Client.Runtimes; using Hypercube.Client.Runtimes.Loop; using Hypercube.Shared.Dependency; using Hypercube.Shared.Entities.Realisation.EventBus; using Hypercube.Shared.Entities.Realisation.Manager; using Hypercube.Shared.EventBus; +using Hypercube.Shared.Resources.Caching; using Hypercube.Shared.Resources.Manager; using Hypercube.Shared.Timing; @@ -46,6 +48,9 @@ public static void Register(DependenciesContainer rootContainer) // Texturing rootContainer.Register(); + // Caching + rootContainer.Register(); + // Camera rootContainer.Register(); diff --git a/Hypercube.Client/Entities/Systems/Sprite/SpriteSystem.cs b/Hypercube.Client/Entities/Systems/Sprite/SpriteSystem.cs index 0fa23d1..64bf90e 100644 --- a/Hypercube.Client/Entities/Systems/Sprite/SpriteSystem.cs +++ b/Hypercube.Client/Entities/Systems/Sprite/SpriteSystem.cs @@ -1,19 +1,21 @@ using Hypercube.Client.Graphics.Drawing; using Hypercube.Client.Graphics.Event; using Hypercube.Client.Graphics.Texturing; +using Hypercube.Client.Resources.Caching; using Hypercube.Shared.Dependency; using Hypercube.Shared.Entities.Realisation; using Hypercube.Shared.Entities.Realisation.Systems; using Hypercube.Shared.Entities.Systems.Transform; using Hypercube.Shared.Math.Transform; using Hypercube.Shared.Math.Vector; +using Hypercube.Shared.Resources.Caching; namespace Hypercube.Client.Entities.Systems.Sprite; public sealed class SpriteSystem : EntitySystem { [Dependency] private readonly IRenderDrawing _drawing = default!; - [Dependency] private readonly ITextureManager _textureManager = default!; + [Dependency] private readonly IResourceCacher _resourceCacher = default!; public override void Initialize() { @@ -34,6 +36,8 @@ private void OnRenderDrawing(ref RenderDrawingEvent ev) public void Render(Entity entity, Transform2 transform) { - _drawing.DrawTexture(entity.Component.TextureHandle ??= _textureManager.GetTextureHandle(entity.Component.TexturePath), Vector2.Zero, entity.Component.Color, transform.Matrix * entity.Component.Transform.Matrix); + entity.Component.TextureHandle ??= + _resourceCacher.GetResource(entity.Component.TexturePath).Texture; + _drawing.DrawTexture(entity.Component.TextureHandle, Vector2.Zero, entity.Component.Color, transform.Matrix * entity.Component.Transform.Matrix); } } \ No newline at end of file diff --git a/Hypercube.Client/Graphics/Drawing/RenderDrawing.cs b/Hypercube.Client/Graphics/Drawing/RenderDrawing.cs index 1dbece4..a3dad51 100644 --- a/Hypercube.Client/Graphics/Drawing/RenderDrawing.cs +++ b/Hypercube.Client/Graphics/Drawing/RenderDrawing.cs @@ -1,18 +1,20 @@ using Hypercube.Client.Graphics.Rendering; using Hypercube.Client.Graphics.Texturing; using Hypercube.Client.Graphics.Texturing.TextureSettings; +using Hypercube.Client.Resources.Caching; using Hypercube.Shared.Dependency; using Hypercube.Shared.Math; using Hypercube.Shared.Math.Box; using Hypercube.Shared.Math.Matrix; using Hypercube.Shared.Math.Vector; +using Hypercube.Shared.Resources.Caching; namespace Hypercube.Client.Graphics.Drawing; public sealed class RenderDrawing : IRenderDrawing { - [Dependency] private readonly ITextureManager _textureManager = default!; [Dependency] private readonly IRenderer _renderer = default!; + [Dependency] private readonly IResourceCacher _resourceCacher = default!; public void DrawTexture(ITexture texture, Vector2 position) { @@ -36,7 +38,8 @@ public void DrawTexture(ITexture texture, Box2 quad, Box2 uv) public void DrawTexture(ITexture texture, Box2 quad, Box2 uv, Color color) { - _renderer.DrawTexture(_textureManager.GetTextureHandle(texture, new Texture2DCreationSettings()), quad, uv, color); + var handle = _resourceCacher.GetResource(texture.Path).Texture; + _renderer.DrawTexture(handle, quad, uv, color); } public void DrawTexture(ITextureHandle texture, Vector2 position, Color color, Matrix4X4 model) diff --git a/Hypercube.Client/Graphics/Event/GraphicsLibraryInitializedEvent.cs b/Hypercube.Client/Graphics/Event/GraphicsLibraryInitializedEvent.cs new file mode 100644 index 0000000..5444069 --- /dev/null +++ b/Hypercube.Client/Graphics/Event/GraphicsLibraryInitializedEvent.cs @@ -0,0 +1,5 @@ +using Hypercube.Shared.EventBus.Events; + +namespace Hypercube.Client.Graphics.Event; + +public readonly record struct GraphicsLibraryInitializedEvent : IEventArgs; \ No newline at end of file diff --git a/Hypercube.Client/Graphics/Rendering/Renderer.OpenGL.cs b/Hypercube.Client/Graphics/Rendering/Renderer.OpenGL.cs index bd1998e..0ded645 100644 --- a/Hypercube.Client/Graphics/Rendering/Renderer.OpenGL.cs +++ b/Hypercube.Client/Graphics/Rendering/Renderer.OpenGL.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using Hypercube.Client.Graphics.Event; using Hypercube.Shared.Logging; using OpenTK.Windowing.GraphicsLibraryFramework; using OpenToolkit.Graphics.OpenGL4; @@ -9,6 +10,12 @@ public sealed partial class Renderer { private const int SwapInterval = 1; + /// + /// This is where we store the callback + /// because otherwise GC will collect it. + /// + private DebugProc? _debugProc; + private void InitOpenGL() { GL.LoadBindings(_bindingsContext); @@ -26,8 +33,9 @@ private void InitOpenGL() GLFW.SwapInterval(SwapInterval); _loggerOpenGL.EngineInfo($"Swap interval: {SwapInterval}"); - - GL.DebugMessageCallback(DebugMessageCallback, IntPtr.Zero); + + _debugProc = DebugMessageCallback; + GL.DebugMessageCallback(_debugProc, IntPtr.Zero); GL.Enable(EnableCap.Blend); GL.Enable(EnableCap.DebugOutput); @@ -36,6 +44,7 @@ private void InitOpenGL() GL.ClearColor(0, 0, 0, 0); _loggerOpenGL.EngineInfo("Initialized"); + _eventBus.Raise(new GraphicsLibraryInitializedEvent()); } private void DebugMessageCallback(DebugSource source, DebugType type, int id, DebugSeverity severity, int length, IntPtr messagePointer, IntPtr userparam) diff --git a/Hypercube.Client/Graphics/Rendering/Renderer.Render.cs b/Hypercube.Client/Graphics/Rendering/Renderer.Render.cs index 6ad7c89..1fd2c69 100644 --- a/Hypercube.Client/Graphics/Rendering/Renderer.Render.cs +++ b/Hypercube.Client/Graphics/Rendering/Renderer.Render.cs @@ -2,6 +2,7 @@ using Hypercube.Client.Graphics.Shading; using Hypercube.Client.Graphics.Texturing; using Hypercube.Client.Graphics.Windows; +using Hypercube.Client.Resources.Caching; using Hypercube.Shared.Math.Matrix; using Hypercube.Shared.Runtimes.Loop.Event; using OpenToolkit.Graphics.OpenGL4; @@ -32,9 +33,7 @@ public sealed partial class Renderer private void OnLoad() { - _baseShader = new Shader("/base", _resourceManager); - _baseTexture = _textureManager.GetTextureHandle("/icon.png"); - _baseTexture.Bind(HTexTarget.Texture2D); + _baseShader = _resourceCacher.GetResource("/Shaders/base").Shader; _cameraManager.SetMainCamera(_cameraManager.CreateCamera2D(MainWindow.Size)); diff --git a/Hypercube.Client/Graphics/Rendering/Renderer.cs b/Hypercube.Client/Graphics/Rendering/Renderer.cs index 25b9fe1..e1e641e 100644 --- a/Hypercube.Client/Graphics/Rendering/Renderer.cs +++ b/Hypercube.Client/Graphics/Rendering/Renderer.cs @@ -1,14 +1,13 @@ using System.Collections.Frozen; using Hypercube.Client.Graphics.OpenGL; using Hypercube.Client.Graphics.Texturing; -using Hypercube.Client.Graphics.Texturing.Events; using Hypercube.Client.Graphics.Viewports; using Hypercube.Client.Graphics.Windows; using Hypercube.Client.Graphics.Windows.Manager; using Hypercube.Shared.Dependency; using Hypercube.Shared.EventBus; -using Hypercube.Shared.EventBus.Events; using Hypercube.Shared.Logging; +using Hypercube.Shared.Resources.Caching; using Hypercube.Shared.Resources.Manager; using Hypercube.Shared.Runtimes.Event; using Hypercube.Shared.Runtimes.Loop.Event; @@ -26,6 +25,7 @@ public sealed partial class Renderer : IRenderer, IPostInject, IEventSubscriber [Dependency] private readonly ITiming _timing = default!; [Dependency] private readonly ICameraManager _cameraManager = default!; [Dependency] private readonly IResourceManager _resourceManager = default!; + [Dependency] private readonly IResourceCacher _resourceCacher = default!; private readonly ILogger _logger = LoggingManager.GetLogger("renderer"); private readonly ILogger _loggerOpenGL = LoggingManager.GetLogger("open_gl")!; @@ -70,24 +70,11 @@ public sealed partial class Renderer : IRenderer, IPostInject, IEventSubscriber public void PostInject() { - _eventBus.Subscribe(this, OnTexturesPreload); - _eventBus.Subscribe(this, OnHandlesPreload); _eventBus.Subscribe(this, OnInitialization); _eventBus.Subscribe(this, OnStartup); _eventBus.Subscribe(this, OnFrameUpdate); _eventBus.Subscribe(this, OnFrameRender); } - - private void OnTexturesPreload(ref TexturesPreloadEvent args) - { - args.Textures.Add("/Icons/image.png"); - args.Textures.Add("/icon.png"); - } - - private void OnHandlesPreload(ref HandlesPreloadEvent args) - { - args.Handles.Add("/icon.png"); - } private void OnInitialization(ref RuntimeInitializationEvent args) { @@ -114,11 +101,7 @@ private void OnStartup(ref RuntimeStartupEvent args) var windowIcons = _windowManager.LoadWindowIcons(_textureManager, _resourceManager, "/Icons").ToList(); _windowManager.SetWindowIcons(MainWindow, windowIcons); - InitOpenGL(); - - _textureManager.CacheHandles(); - OnLoad(); } diff --git a/Hypercube.Client/Graphics/Shading/IShader.cs b/Hypercube.Client/Graphics/Shading/IShader.cs index 50e5533..c655d6e 100644 --- a/Hypercube.Client/Graphics/Shading/IShader.cs +++ b/Hypercube.Client/Graphics/Shading/IShader.cs @@ -2,7 +2,7 @@ namespace Hypercube.Client.Graphics.Shading; -public interface IShader +public interface IShader : IDisposable { void Use(); void Stop(); diff --git a/Hypercube.Client/Graphics/Shading/Shader.cs b/Hypercube.Client/Graphics/Shading/Shader.cs index 65bf371..b3bff85 100644 --- a/Hypercube.Client/Graphics/Shading/Shader.cs +++ b/Hypercube.Client/Graphics/Shading/Shader.cs @@ -141,4 +141,8 @@ private static void LinkProgram(int program) } //private readonly record struct AttributeInfo + public void Dispose() + { + GL.DeleteProgram(_handle); + } } \ No newline at end of file diff --git a/Hypercube.Client/Graphics/Texturing/ITextureManager.cs b/Hypercube.Client/Graphics/Texturing/ITextureManager.cs index a1e85a9..aa38410 100644 --- a/Hypercube.Client/Graphics/Texturing/ITextureManager.cs +++ b/Hypercube.Client/Graphics/Texturing/ITextureManager.cs @@ -6,7 +6,6 @@ namespace Hypercube.Client.Graphics.Texturing; public interface ITextureManager { ITexture GetTexture(ResourcePath path); - void CacheHandles(); ITextureHandle GetTextureHandle(ResourcePath path, ITextureCreationSettings settings); ITextureHandle GetTextureHandle(ResourcePath path); diff --git a/Hypercube.Client/Graphics/Texturing/TextureManager.cs b/Hypercube.Client/Graphics/Texturing/TextureManager.cs index adac0d3..e947959 100644 --- a/Hypercube.Client/Graphics/Texturing/TextureManager.cs +++ b/Hypercube.Client/Graphics/Texturing/TextureManager.cs @@ -4,23 +4,17 @@ using Hypercube.Client.Graphics.Texturing.Events; using Hypercube.Client.Graphics.Texturing.TextureSettings; using Hypercube.Shared.Dependency; -using Hypercube.Shared.EventBus; using Hypercube.Shared.Logging; using Hypercube.Shared.Resources; using Hypercube.Shared.Resources.Manager; -using Hypercube.Shared.Runtimes.Event; using StbImageSharp; namespace Hypercube.Client.Graphics.Texturing; -public sealed class TextureManager : ITextureManager, IEventSubscriber, IPostInject +public sealed class TextureManager : ITextureManager { - [Dependency] private readonly IEventBus _eventBus = default!; [Dependency] private readonly IResourceManager _resourceManager = default!; - private FrozenDictionary _cachedHandles = FrozenDictionary.Empty; - private FrozenDictionary _cachedTextures = FrozenDictionary.Empty; - private readonly Logger _logger = LoggingManager.GetLogger("texturing"); public TextureManager() @@ -28,69 +22,6 @@ public TextureManager() StbImage.stbi_set_flip_vertically_on_load(1); } - public void PostInject() - { - _eventBus.Subscribe(this, OnInitialization); - } - - private void OnInitialization(ref RuntimeInitializationEvent args) - { - _logger.EngineInfo("Caching textures..."); - var st = Stopwatch.StartNew(); - var ev = new TexturesPreloadEvent(new HashSet()); - var textures = new Dictionary(); - _eventBus.Raise(ref ev); - - foreach (var texturePath in ev.Textures) - { - if (textures.ContainsKey(texturePath)) - continue; - - var texture = CreateTexture(texturePath); - textures.Add(texturePath, texture); - } - - CacheTextures(textures); - st.Stop(); - _logger.EngineInfo($"Cached {_cachedTextures.Count} textures in {st.Elapsed}"); - } - - public void CacheHandles() - { - _logger.EngineInfo("Caching handles..."); - var st = Stopwatch.StartNew(); - - var ev = new HandlesPreloadEvent(new HashSet()); - _eventBus.Raise(ref ev); - - var handles = new Dictionary(); - - foreach (var handlePath in ev.Handles) - { - if (handles.ContainsKey(handlePath)) - continue; - - ITextureHandle handle; - - if (_cachedTextures.TryGetValue(handlePath, out var texture)) - { - handle = CreateTextureHandle(texture, new Texture2DCreationSettings()); - handles.Add(handlePath, handle); - continue; - } - - handle = CreateTextureHandle(handlePath, new Texture2DCreationSettings()); - handles.Add(handlePath, handle); - } - - CacheHandles(handles); - st.Stop(); - - _logger.EngineInfo($"Cached {_cachedHandles.Count} handles in {st.Elapsed}"); - } - - #region PublicAPI - public ITextureHandle GetTextureHandle(ResourcePath path, ITextureCreationSettings settings) { return GetTextureHandleInternal(path, settings); @@ -116,19 +47,9 @@ public ITextureHandle GetTextureHandle(ITexture texture, ITextureCreationSetting return GetTextureHandleInternal(texture.Path, settings); } - #endregion - - #region Internal - internal ITexture GetTextureInternal(ResourcePath path) { - if (_cachedTextures.TryGetValue(path, out var value)) - return value; - - // fallback to low performance method var texture = CreateTexture(path); - CacheTexture(texture); - return texture; } @@ -144,26 +65,7 @@ internal ITexture CreateTexture(ResourcePath path) internal ITextureHandle GetTextureHandleInternal(ResourcePath path, ITextureCreationSettings settings) { - if (_cachedHandles.TryGetValue(path, out var value)) - return value; - - ITextureHandle handle; - - if (_cachedTextures.TryGetValue(path, out var texture)) - { - // low performance method fallback - handle = CreateTextureHandle(texture, settings); - CacheHandle(handle); - - return handle; - } - - // fallback to low performance method - handle = CreateTextureHandle(path, settings); - - CacheHandle(handle); - - return handle; + return CreateTextureHandle(path, settings); } internal ITextureHandle CreateTextureHandle(ResourcePath path, ITextureCreationSettings settings) @@ -178,43 +80,4 @@ internal ITextureHandle CreateTextureHandle(ITexture texture, ITextureCreationSe { return new TextureHandle(texture, settings); } - - /// - /// Extremely high impact on performance, use when you know what you're doing - /// - internal void CacheTexture(ITexture texture) - { - var cached = _cachedTextures.ToDictionary(); - cached.Add(texture.Path, texture); - _cachedTextures = cached.ToFrozenDictionary(); - _logger.Warning($"Cached texture with path {texture.Path} in runtime"); - } - - /// - /// Extremely high impact on performance, use when you know what you're doing - /// - internal void CacheHandle(ITextureHandle texture) - { - var cached = _cachedHandles.ToDictionary(); - cached.Add(texture.Texture.Path, texture); - _cachedHandles = cached.ToFrozenDictionary(); - _logger.Warning($"Cached handle with path {texture.Texture.Path} in runtime"); - } - - #endregion - - #region Preloading - - internal void CacheTextures(Dictionary textures) - { - _cachedTextures = textures.ToFrozenDictionary(); - } - - internal void CacheHandles(Dictionary handles) - { - _cachedHandles = handles.ToFrozenDictionary(); - } - - #endregion - } \ No newline at end of file diff --git a/Hypercube.Client/Graphics/Texturing/TextureSettings/Texture2DCreationSettings.cs b/Hypercube.Client/Graphics/Texturing/TextureSettings/Texture2DCreationSettings.cs index 984d7bc..d0677b5 100644 --- a/Hypercube.Client/Graphics/Texturing/TextureSettings/Texture2DCreationSettings.cs +++ b/Hypercube.Client/Graphics/Texturing/TextureSettings/Texture2DCreationSettings.cs @@ -36,8 +36,8 @@ public Texture2DCreationSettings() { new TextureParameter(TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat), new TextureParameter(TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat), - new TextureParameter(TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear), - new TextureParameter(TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear), + new TextureParameter(TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest), + new TextureParameter(TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest), }; PixelInternalFormat = PixelInternalFormat.Rgba; Level = 0; diff --git a/Hypercube.Client/Resources/Caching/AudioSourceResource.cs b/Hypercube.Client/Resources/Caching/AudioSourceResource.cs new file mode 100644 index 0000000..997e7b9 --- /dev/null +++ b/Hypercube.Client/Resources/Caching/AudioSourceResource.cs @@ -0,0 +1,18 @@ +using Hypercube.Client.Audio; +using Hypercube.Shared.Dependency; +using Hypercube.Shared.Resources; +using Hypercube.Shared.Resources.Caching.Resource; + +namespace Hypercube.Client.Resources.Caching; + +public sealed class AudioSourceResource : Resource +{ + public ResourcePath Path; + public AudioStream Stream; + + public override void Load(ResourcePath path, DependenciesContainer container) + { + var audioMan = container.Resolve(); + Stream = audioMan.GetAudio(path, new AudioSettings()); + } +} \ No newline at end of file diff --git a/Hypercube.Client/Resources/Caching/ResourceCacher.Preload.cs b/Hypercube.Client/Resources/Caching/ResourceCacher.Preload.cs new file mode 100644 index 0000000..b47e1d8 --- /dev/null +++ b/Hypercube.Client/Resources/Caching/ResourceCacher.Preload.cs @@ -0,0 +1,91 @@ +using System.Diagnostics; +using Hypercube.Client.Audio.Event; +using Hypercube.Client.Graphics.Event; +using Hypercube.Shared.Dependency; +using Hypercube.Shared.Logging; + +namespace Hypercube.Client.Resources.Caching; + +public partial class ResourceCacher +{ + private readonly Logger _loggerPreload = LoggingManager.GetLogger("cache.preload"); + + private void OnAudioLibraryInitialized(ref AudioLibraryInitializedEvent ev) + { + PreloadAudio(_container); + } + + private void OnGraphicsLibraryInitialized(ref GraphicsLibraryInitializedEvent ev) + { + PreloadTextures(_container); + PreloadShaders(_container); + } + + private void PreloadTextures(DependenciesContainer container) + { + _loggerPreload.EngineInfo("Preloading textures..."); + var st = Stopwatch.StartNew(); + + var texDict = GetTypeDict(); + + var files = _resourceManager.FindContentFiles("/Textures/") + .Where(p => !texDict.ContainsKey(p) && p.Extension == ".png") + .Select(p => new TextureResource { Path = p}); + + // TODO: Find a way of making Parallel.ForEach, currently it causes AccessViolation ex + var count = 0; + foreach (var file in files) + { + file.Load(file.Path, container); + texDict[file.Path] = file; + count++; + } + st.Stop(); + _logger.EngineInfo($"Preloaded {count} textures in {st.Elapsed}"); + } + + private void PreloadShaders(DependenciesContainer container) + { + _loggerPreload.EngineInfo("Preloading shaders..."); + var st = Stopwatch.StartNew(); + + var shDict = GetTypeDict(); + + var files = _resourceManager.FindContentFiles("/Shaders/") + .Where(p => !shDict.ContainsKey(p) && p.Extension == ".vert") + .Select(p => new ShaderSourceResource { Base = $"{p.ParentDirectory}/{p.Filename}", VertexPath = $"{p.ParentDirectory}/{p.Filename}.vert", FragmentPath = $"{p.ParentDirectory}/{p.Filename}.frag"}); + + var count = 0; + // TODO: Find a way of making Parallel.ForEach, currently it causes AccessViolation ex + foreach (var file in files) + { + file.Load(file.Base, container); + shDict[file.Base] = file; + count++; + } + st.Stop(); + _logger.EngineInfo($"Preloaded {count} shaders in {st.Elapsed}"); + } + + private void PreloadAudio(DependenciesContainer container) + { + _loggerPreload.EngineInfo("Preloading shaders..."); + var st = Stopwatch.StartNew(); + + var aDict = GetTypeDict(); + + var files = _resourceManager.FindContentFiles("/Audio/") + .Where(p => !aDict.ContainsKey(p) && p.Extension == ".wav" || p.Extension == ".ogg") + .Select(p => new AudioSourceResource() {Path = p}); + + var count = 0; + foreach (var file in files) + { + file.Load(file.Path, container); + aDict[file.Path] = file; + count++; + } + st.Stop(); + _logger.EngineInfo($"Preloaded {count} audio files in {st.Elapsed}"); + } +} \ No newline at end of file diff --git a/Hypercube.Client/Resources/Caching/ResourceCacher.cs b/Hypercube.Client/Resources/Caching/ResourceCacher.cs new file mode 100644 index 0000000..fed2466 --- /dev/null +++ b/Hypercube.Client/Resources/Caching/ResourceCacher.cs @@ -0,0 +1,100 @@ +using System.Diagnostics.CodeAnalysis; +using Hypercube.Client.Audio.Event; +using Hypercube.Client.Graphics.Event; +using Hypercube.Shared.Dependency; +using Hypercube.Shared.EventBus; +using Hypercube.Shared.Logging; +using Hypercube.Shared.Resources; +using Hypercube.Shared.Resources.Caching; +using Hypercube.Shared.Resources.Caching.Resource; +using Hypercube.Shared.Resources.Manager; + +namespace Hypercube.Client.Resources.Caching; + +public partial class ResourceCacher : IResourceCacher, IEventSubscriber, IPostInject +{ + [Dependency] private readonly IEventBus _eventBus = default!; + [Dependency] private readonly IResourceManager _resourceManager = default!; + + private readonly Dictionary> _cachedResources = new(); + private readonly DependenciesContainer _container = DependencyManager.GetContainer(); + private readonly Logger _logger = LoggingManager.GetLogger("cache"); + + public void PostInject() + { + _eventBus.Subscribe(this, OnAudioLibraryInitialized); + _eventBus.Subscribe(this, OnGraphicsLibraryInitialized); + } + + public T GetResource(ResourcePath path, bool useFallback = true) where T : Resource, new() + { + var typeDict = GetTypeDict(); + + if (typeDict.TryGetValue(path, out var cache)) + return (T) cache; + + + cache = new T(); + try + { + cache.Load(path, _container); + typeDict[path] = cache; + return (T) cache; + } + catch (Exception ex) + { + if (useFallback && cache.FallbackPath is not null) + return GetResource(cache.FallbackPath.Value, false); + + _logger.Fatal($"Exception while loading resource {ex.Message}, Stack Trace: {ex.StackTrace}"); + throw; + } + } + + public bool TryGetResource(ResourcePath path, [NotNullWhen(true)] out T? resource) where T : Resource, new() + { + var cont = DependencyManager.GetContainer(); + var cache = GetTypeDict(); + + if (cache.TryGetValue(path, out var res)) + { + resource = (T)res; + return true; + } + + res = new T(); + try + { + res.Load(path, cont); + cache[path] = res; + resource = (T)res; + return true; + } + catch (FileNotFoundException) + { + resource = null; + return false; + } + catch (Exception ex) + { + _logger.Fatal($"Exception while loading resource: {ex.Message}, Stack Trace: {ex.StackTrace}"); + throw; + } + } + + public void CacheResource(ResourcePath path, T resource) where T : Resource, new() + { + GetTypeDict()[path] = resource; + } + + private Dictionary GetTypeDict() + { + if (_cachedResources.TryGetValue(typeof(T), out var dict)) + return dict; + + dict = new Dictionary(); + _cachedResources[typeof(T)] = dict; + + return dict; + } +} \ No newline at end of file diff --git a/Hypercube.Client/Resources/Caching/ShaderSourceResource.cs b/Hypercube.Client/Resources/Caching/ShaderSourceResource.cs new file mode 100644 index 0000000..c3e4969 --- /dev/null +++ b/Hypercube.Client/Resources/Caching/ShaderSourceResource.cs @@ -0,0 +1,25 @@ +using Hypercube.Client.Graphics.Shading; +using Hypercube.Shared.Dependency; +using Hypercube.Shared.Resources; +using Hypercube.Shared.Resources.Caching.Resource; +using Hypercube.Shared.Resources.Manager; + +namespace Hypercube.Client.Resources.Caching; + +public sealed class ShaderSourceResource : Resource, IDisposable +{ + public IShader Shader; + public string Base; + public ResourcePath VertexPath; + public ResourcePath FragmentPath; + + public override void Load(ResourcePath path, DependenciesContainer container) + { + Shader = new Shader(path, container.Resolve()); + } + + public void Dispose() + { + Shader.Dispose(); + } +} \ No newline at end of file diff --git a/Hypercube.Client/Resources/Caching/TextureResource.cs b/Hypercube.Client/Resources/Caching/TextureResource.cs new file mode 100644 index 0000000..6e69756 --- /dev/null +++ b/Hypercube.Client/Resources/Caching/TextureResource.cs @@ -0,0 +1,26 @@ +using Hypercube.Client.Graphics.Texturing; +using Hypercube.Client.Graphics.Texturing.TextureSettings; +using Hypercube.Shared.Dependency; +using Hypercube.Shared.Resources; +using Hypercube.Shared.Resources.Caching.Resource; +using Hypercube.Shared.Resources.Manager; + +namespace Hypercube.Client.Resources.Caching; + +public sealed class TextureResource : Resource, IDisposable +{ + public ITextureHandle Texture; + public ResourcePath Path; + + public override void Load(ResourcePath path, DependenciesContainer container) + { + var textureManager = container.Resolve(); + var handle = textureManager.GetTextureHandle(path, new Texture2DCreationSettings()); + Texture = handle; + } + + public void Dispose() + { + Texture.Dispose(); + } +} \ No newline at end of file diff --git a/Hypercube.Example/Example.cs b/Hypercube.Example/Example.cs index 7433334..2dea00d 100644 --- a/Hypercube.Example/Example.cs +++ b/Hypercube.Example/Example.cs @@ -1,11 +1,13 @@ using Hypercube.Client.Audio; using Hypercube.Client.Entities.Systems.Sprite; +using Hypercube.Client.Resources.Caching; using Hypercube.Shared.Dependency; using Hypercube.Shared.Entities.Realisation.Manager; using Hypercube.Shared.Entities.Systems.Transform.Coordinates; using Hypercube.Shared.EventBus; using Hypercube.Shared.Math.Vector; using Hypercube.Shared.Resources; +using Hypercube.Shared.Resources.Caching; using Hypercube.Shared.Runtimes.Event; using Hypercube.Shared.Scenes; @@ -14,6 +16,7 @@ namespace Hypercube.Example; public sealed class Example : IEventSubscriber, IPostInject { [Dependency] private readonly IAudioManager _audioManager = default!; + [Dependency] private readonly IResourceCacher _resourceCacher = default!; [Dependency] private readonly IEventBus _eventBus = default!; [Dependency] private readonly IEntitiesManager _entitiesManager = default!; [Dependency] private readonly IEntitiesComponentManager _entitiesComponentManager = default!; @@ -32,7 +35,7 @@ public void PostInject() private void Startup(ref RuntimeStartupEvent args) { - for (var i = 0; i < 300; i++) + for (var i = 0; i < 100; i++) { var x = _random.NextSingle() * 800 - 400; var y = _random.NextSingle() * 800 - 400; @@ -41,8 +44,14 @@ private void Startup(ref RuntimeStartupEvent args) CreateEntity(coord); } - var source = _audioManager.CreateSource("/game_boi_3.wav", new AudioSettings()); + var stream = _resourceCacher.GetResource("/game_boi_3.wav").Stream; + var source = _audioManager.CreateSource(stream); + + // it's too loud :D + source.Gain = 0.1f; source.Start(); + // var source = _audioManager.CreateSource("/game_boi_3.wav", new AudioSettings()); + // source.Start(); } private void CreateEntity(SceneCoordinates coordinates) @@ -51,7 +60,7 @@ private void CreateEntity(SceneCoordinates coordinates) var sprite = _entitiesComponentManager.AddComponent(entity); var example = _entitiesComponentManager.AddComponent(entity); - sprite.TexturePath = new ResourcePath("/icon.png"); + sprite.TexturePath = new ResourcePath("/Textures/icon.png"); example.Offset = _random.Next(0, 1000); } } \ No newline at end of file diff --git a/Hypercube.Shared/Dependency/DependencyManager.cs b/Hypercube.Shared/Dependency/DependencyManager.cs index 33ced29..a953275 100644 --- a/Hypercube.Shared/Dependency/DependencyManager.cs +++ b/Hypercube.Shared/Dependency/DependencyManager.cs @@ -75,4 +75,10 @@ public static DependenciesContainer Create() Debug.Assert(_container.IsValueCreated); return new DependenciesContainer(_container.Value!); } + + public static DependenciesContainer GetContainer() + { + Debug.Assert(_container.IsValueCreated); + return _container.Value!; + } } \ No newline at end of file diff --git a/Hypercube.Shared/Resources/Caching/IResourceCacher.cs b/Hypercube.Shared/Resources/Caching/IResourceCacher.cs new file mode 100644 index 0000000..08deb21 --- /dev/null +++ b/Hypercube.Shared/Resources/Caching/IResourceCacher.cs @@ -0,0 +1,14 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Hypercube.Shared.Resources.Caching; + +public interface IResourceCacher +{ + T GetResource(ResourcePath path, bool useFallback = true) where T : Resource.Resource, new(); + + bool TryGetResource(ResourcePath path, [NotNullWhen(true)] out T? resource) + where T : Resource.Resource, new(); + + void CacheResource(ResourcePath path, T resource) + where T : Resource.Resource, new(); +} \ No newline at end of file diff --git a/Hypercube.Shared/Resources/Caching/Resource/Resource.cs b/Hypercube.Shared/Resources/Caching/Resource/Resource.cs new file mode 100644 index 0000000..124a70c --- /dev/null +++ b/Hypercube.Shared/Resources/Caching/Resource/Resource.cs @@ -0,0 +1,14 @@ +using Hypercube.Shared.Dependency; + +namespace Hypercube.Shared.Resources.Caching.Resource; + +public abstract class Resource +{ + public ResourcePath? FallbackPath { get; } + + public abstract void Load(ResourcePath path, DependenciesContainer container); + + public virtual void Reload(ResourcePath path, DependenciesContainer container) + { + } +} \ No newline at end of file