Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Content Manager #276

Merged
merged 37 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f93e651
start implementing node graph
LeNitrous Jul 14, 2023
0762ed8
update Sekai
LeNitrous Jul 14, 2023
a544bdc
remove previous rendering impl
LeNitrous Jul 14, 2023
cddf178
add coordinate providing interfaces
LeNitrous Jul 14, 2023
36e3e00
add `Node`
LeNitrous Jul 14, 2023
738f467
add `Effect`
LeNitrous Jul 14, 2023
16c4b28
add material-related interfaces
LeNitrous Jul 14, 2023
b7ac6d6
add `ShaderMaterial`
LeNitrous Jul 14, 2023
51a4f8a
add `UnlitMaterial`
LeNitrous Jul 14, 2023
e617ca5
add `Behavior`
LeNitrous Jul 14, 2023
3287a4e
add `Renderable`
LeNitrous Jul 14, 2023
0d6d8c4
add `RenderGroup`s
LeNitrous Jul 14, 2023
07b1a50
add `RenderObject`s
LeNitrous Jul 14, 2023
702ccce
add `RenderData`
LeNitrous Jul 14, 2023
27f904b
add `RenderQueue` and `RenderContext`
LeNitrous Jul 14, 2023
f99c0b7
add `RenderTarget`
LeNitrous Jul 14, 2023
f58ec7e
add `Renderer`
LeNitrous Jul 14, 2023
3f07cd2
add `SortedFilteredCollection<T>`
LeNitrous Jul 14, 2023
536e586
add `Drawable`
LeNitrous Jul 14, 2023
48b8a7b
add projectors
LeNitrous Jul 14, 2023
194e1e1
add `World`
LeNitrous Jul 14, 2023
072cc4e
add `Window` node
LeNitrous Jul 14, 2023
f3a3e54
add executable
LeNitrous Jul 14, 2023
54940e9
ensure material resources are applied
LeNitrous Jul 14, 2023
64e87b4
remove `Renderable`
LeNitrous Jul 14, 2023
347daea
cleanup pass
LeNitrous Jul 14, 2023
37fa552
add `ServiceLocator`
LeNitrous Jul 15, 2023
36919c2
add `Node.Services`
LeNitrous Jul 15, 2023
2398f20
make `Window` override `Services`
LeNitrous Jul 15, 2023
b8b57fd
refactor `World`
LeNitrous Jul 15, 2023
ea57253
start implementing content manager
LeNitrous Jul 15, 2023
1a1f6cd
add `ContentManager` and `IContentLoader`
LeNitrous Jul 15, 2023
6edf561
add shader and texture loaders
LeNitrous Jul 15, 2023
ac987f9
include stbisharp
LeNitrous Jul 15, 2023
94f201d
load content manager
LeNitrous Jul 15, 2023
3313c7a
add more constructors to `UnlitMaterial`
LeNitrous Jul 15, 2023
c57f399
Merge branch 'main' into feature-content-manager
sr229 Jul 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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