Skip to content

Commit

Permalink
Add Content Manager (#276)
Browse files Browse the repository at this point in the history
* start implementing node graph

* update Sekai

* remove previous rendering impl

* add coordinate providing interfaces

* add `Node`

* add `Effect`

* add material-related interfaces

* add `ShaderMaterial`

* add `UnlitMaterial`

* add `Behavior`

* add `Renderable`

* add `RenderGroup`s

* add `RenderObject`s

* add `RenderData`

* add `RenderQueue` and `RenderContext`

* add `RenderTarget`

* add `Renderer`

* add `SortedFilteredCollection<T>`

* add `Drawable`

* add projectors

* add `World`

* add `Window` node

* add executable

* ensure material resources are applied

* remove `Renderable`

* cleanup pass

* add `ServiceLocator`

* add `Node.Services`

* make `Window` override `Services`

* refactor `World`

* start implementing content manager

* add `ContentManager` and `IContentLoader`

* add shader and texture loaders

* include stbisharp

* load content manager

* add more constructors to `UnlitMaterial`

---------

Co-authored-by: Ayase Minori <[email protected]>
  • Loading branch information
LeNitrous and sr229 authored Jul 16, 2023
1 parent b434ee6 commit fd4e1c8
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 1 deletion.
153 changes: 153 additions & 0 deletions source/Vignette/Content/ContentManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright (c) Cosyne
// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Sekai.Storages;

namespace Vignette.Content;

/// <summary>
/// Manages content.
/// </summary>
public sealed class ContentManager
{
private readonly Storage storage;
private readonly HashSet<string> extensions = new();
private readonly HashSet<IContentLoader> loaders = new();
private readonly Dictionary<ContentKey, WeakReference> content = new();

internal ContentManager(Storage storage)
{
this.storage = storage;
}

/// <summary>
/// Reads a file from <see cref="Storage"/> and loads it as <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of content to load.</typeparam>
/// <param name="path">The path to the content.</param>
/// <returns>The loaded content.</returns>
/// <exception cref="ArgumentException">Thrown when invalid path has been passed.</exception>
public T Load<T>([StringSyntax(StringSyntaxAttribute.Uri)] string path)
where T : class
{
string ext = Path.GetExtension(path);

if (string.IsNullOrEmpty(ext))
{
throw new ArgumentException($"Failed to determine file extension.", nameof(path));
}

if (!extensions.Contains(ext))
{
throw new ArgumentException($"Cannot load unsupported file extension \"{ext}\".");
}

var key = new ContentKey(typeof(T), path);

if (!content.TryGetValue(key, out var weak))
{
weak = new WeakReference(null);
content.Add(key, weak);
}

if (!weak.IsAlive)
{
weak.Target = Load<T>(storage.Open(path, FileMode.Open, FileAccess.Read));
}

return (T)weak.Target!;
}

/// <summary>
/// Loads a <see cref="Stream"/> as <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of content to load.</typeparam>
/// <param name="stream">The stream to be read.</param>
/// <returns>The loaded content.</returns>
/// <exception cref="InvalidOperationException">Thrown when the stream can't be read.</exception>
public T Load<T>(Stream stream)
where T : class
{
Span<byte> buffer = stackalloc byte[(int)stream.Length];

if (stream.Read(buffer) <= 0)
{
throw new InvalidOperationException("Failed to read stream.");
}

return Load<T>((ReadOnlySpan<byte>)buffer);
}

/// <summary>
/// Loads a <see cref="byte[]"/> as <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of content to load.</typeparam>
/// <param name="bytes">The byte data to be read.</param>
/// <returns>The loaded content.</returns>
public T Load<T>(byte[] bytes)
where T : class
{
return Load<T>(bytes);
}

/// <summary>
/// Loads a <see cref="ReadOnlySpan{byte}"/> as <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of content to load.</typeparam>
/// <param name="bytes">The byte data to be read.</param>
/// <returns>The loaded content.</returns>
/// <exception cref="InvalidOperationException">Thrown when no loader was able to load the content.</exception>
public T Load<T>(ReadOnlySpan<byte> bytes)
where T : class
{
var result = default(T);

foreach (var loader in loaders)
{
if (loader is not IContentLoader<T> typedLoader)
{
continue;
}

try
{
result = typedLoader.Load(bytes);
break;
}
catch
{
}
}

if (result is null)
{
throw new InvalidOperationException("Failed to load content.");
}

return result;
}

/// <summary>
/// Adds a content loader to the content manager.
/// </summary>
/// <param name="loader">The content loader to add.</param>
/// <param name="extensions">The file extensions supported by this loader.</param>
/// <exception cref="InvalidOperationException">Thrown when </exception>
internal void Add(IContentLoader loader, params string[] extensions)
{
foreach (string extension in extensions)
{
string ext = extension.StartsWith(ext_separator) ? extension : ext_separator + extension;
this.loaders.Add(loader);
this.extensions.Add(ext);
}
}

private const char ext_separator = '.';

private readonly record struct ContentKey(Type Type, string Path);
}
28 changes: 28 additions & 0 deletions source/Vignette/Content/IContentLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Cosyne
// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details.

using System;

namespace Vignette.Content;

/// <summary>
/// Defines a mechanism for loading content.
/// </summary>
public interface IContentLoader
{
}

/// <summary>
/// Defines a mechanism for loading <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of content it loads.</typeparam>
public interface IContentLoader<T> : IContentLoader
where T : class
{
/// <summary>
/// Loads a <see cref="ReadOnlySpan{byte}"/> as <typeparamref name="T"/>.
/// </summary>
/// <param name="bytes">The byte data to be read.</param>
/// <returns>The loaded content.</returns>
T Load(ReadOnlySpan<byte> bytes);
}
16 changes: 16 additions & 0 deletions source/Vignette/Content/ShaderLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Cosyne
// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details.

using System;
using System.Text;
using Vignette.Graphics;

namespace Vignette.Content;

internal sealed class ShaderLoader : IContentLoader<ShaderMaterial>
{
public ShaderMaterial Load(ReadOnlySpan<byte> bytes)
{
return ShaderMaterial.Create(Encoding.UTF8.GetString(bytes));
}
}
37 changes: 37 additions & 0 deletions source/Vignette/Content/TextureLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Cosyne
// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details.

using System;
using Sekai.Graphics;
using StbiSharp;

namespace Vignette.Content;

internal sealed class TextureLoader : IContentLoader<Texture>
{
private readonly GraphicsDevice device;

public TextureLoader(GraphicsDevice device)
{
this.device = device;
}

public Texture Load(ReadOnlySpan<byte> bytes)
{
var image = Stbi.LoadFromMemory(bytes, 4);

var texture = device.CreateTexture(new TextureDescription
(
image.Width,
image.Height,
PixelFormat.R8G8B8A8_UNorm,
1,
1,
TextureUsage.Resource
));

texture.SetData(image.Data, 0, 0, 0, 0, 0, image.Width, image.Height, 0);

return texture;
}
}
13 changes: 13 additions & 0 deletions source/Vignette/Graphics/UnlitMaterial.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ public UnlitMaterial()
{
}

public UnlitMaterial(Texture texture)
: this(false)
{
Texture = texture;
}

public UnlitMaterial(Texture texture, Sampler sampler)
: this(false)
{
Texture = texture;
Sampler = sampler;
}

private UnlitMaterial(bool isDefault)
{
effect = Effect.From(shader, out layout, out properties);
Expand Down
1 change: 1 addition & 0 deletions source/Vignette/Vignette.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<ItemGroup>
<PackageReference Include="Sekai" Version="0.1.0-alpha.9" />
<PackageReference Include="StbiSharp" Version="1.2.1" />
</ItemGroup>

</Project>
19 changes: 18 additions & 1 deletion source/Vignette/VignetteGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,34 @@

using System;
using Sekai;
using Vignette.Content;
using Vignette.Graphics;

namespace Vignette;

public sealed class VignetteGame : Game
{
private Window root = null!;
private Camera camera = null!;
private Renderer renderer = null!;
private readonly Window root = new();
private ContentManager content = null!;
private ServiceLocator services = null!;

public override void Load()
{
content = new(Storage);
content.Add(new ShaderLoader(), ".hlsl");
content.Add(new TextureLoader(Graphics), ".png", ".jpg", ".jpeg", ".bmp", ".gif");

renderer = new(Graphics);

services = new();
services.Add(content);

root = new(services)
{
(camera = new Camera { ProjectionMode = CameraProjectionMode.OrthographicOffCenter })
};
}

public override void Draw()
Expand All @@ -24,6 +40,7 @@ public override void Draw()

public override void Update(TimeSpan elapsed)
{
camera.ViewSize = Window.Size;
root.Update(elapsed);
}

Expand Down

0 comments on commit fd4e1c8

Please sign in to comment.