Work in progress new asset system.

This commit is contained in:
Harrison Deng 2020-11-17 13:10:44 -06:00
parent d8eda24809
commit 8b2c26f9bb
13 changed files with 183 additions and 51 deletions

View File

@ -0,0 +1,26 @@
using SlatedGameToolkit.Framework.AssetSystem;
using SlatedGameToolkit.Framework.Graphics.OpenGL;
using SlatedGameToolkit.Framework.Graphics.Textures;
using SlatedGameToolkit.Framework.Graphics.Window;
namespace SlatedGameToolkit.Framework.AssetSystem.AssetData
{
public class TextureData : IAssetData
{
public readonly byte[] data;
public readonly int width;
public readonly int height;
public TextureData(int width, int height, byte[] data) {
this.data = data;
this.width = width;
this.height = height;
}
public IAssetUseable InitializeUseable(GLContext glContext = null)
{
if (glContext == null) glContext = WindowContextsManager.CurrentGL;
return new Texture(this, context: glContext);
}
}
}

View File

@ -14,23 +14,29 @@ namespace SlatedGameToolkit.Framework.AssetSystem {
/// <param name="path">A path to modify.</param>
/// <returns>The modified path.</returns>
public delegate string ModifyPath(string path);
/// <summary>
/// A delegate that performs the loading of the specific asset.
/// </summary>
/// <param name="path">The path of the asset.</param>
/// <param name="glContext">The context to be used.</param>
/// <param name="name">The path of the asset.</param>
/// <typeparam name="T">The type of the resulting asset.</typeparam>
/// <returns>A loaded, useable asset.</returns>
public delegate T LoadAsset<T>(string path, GLContext glContext) where T : IAssetUseable;
public delegate IAssetData LoadAsset(string path);
//TODO Implement change to two step asset pipeline.
public class AssetManager {
public IAssetUseable this[string key] {
get {
if (!this.assets.ContainsKey(key)) { // if the data is still uninitialized, then do so.
InitializeData(key);
}
return this.assets[key];
}
}
private readonly object assetThreadLock = new object();
private Thread thread;
private ConcurrentQueue<(string, GLContext)> loadables;
private ConcurrentQueue<string> loadables;
private volatile bool load;
public bool Complete {
get {
@ -57,12 +63,19 @@ namespace SlatedGameToolkit.Framework.AssetSystem {
/// All file extensions will be requested in lower cases, and therefore, the extension key in this dictionary should also be all lowercase.
/// </summary>
/// <value>The dictionary that associates the loaders to their file extensions.</value>
public ConcurrentDictionary<string, LoadAsset<IAssetUseable>> Loaders { get ; private set; }
public AssetManager() {
this.loadables = new ConcurrentQueue<(string, GLContext)>();
public ConcurrentDictionary<string, LoadAsset> Loaders { get ; private set; }
private ConcurrentDictionary<string, IAssetData> uninitializedDataDictionary;
private ConcurrentBag<string> uninitializedDataBag;
private GLContext glContext;
public AssetManager(GLContext glContext = null) {
this.glContext = glContext ?? WindowContextsManager.CurrentGL;
this.loadables = new ConcurrentQueue<string>();
this.assets = new ConcurrentDictionary<string, IAssetUseable>();
this.PathModifiers = new ConcurrentDictionary<string, ModifyPath>();
this.Loaders = new ConcurrentDictionary<string, LoadAsset<IAssetUseable>>();
this.Loaders = new ConcurrentDictionary<string, LoadAsset>();
this.uninitializedDataDictionary = new ConcurrentDictionary<string, IAssetData>();
this.uninitializedDataBag = new ConcurrentBag<string>();
thread = new Thread(Run);
this.load = true;
@ -77,20 +90,19 @@ namespace SlatedGameToolkit.Framework.AssetSystem {
/// The file name of the loadable is the one the loadable is saved under.
/// </summary>
/// <param name="name">path of file.</param>
public void QueueLoad(string name, GLContext glContext = null) {
if (glContext == null) glContext = WindowContextsManager.CurrentGL;
loadables.Enqueue((name, glContext));
public void QueueLoad(string name) {
loadables.Enqueue(name);
}
private void Run() {
while (load) {
lock (assetThreadLock)
{
ValueTuple<string, GLContext> loadable;
string loadable;
while (loadables.TryDequeue(out loadable)) {
Load(loadable.Item1, loadable.Item2);
Load(loadable);
}
if (load) Monitor.Wait(assetThreadLock);
if (load) Monitor.Wait(assetThreadLock);
}
}
}
@ -99,8 +111,7 @@ namespace SlatedGameToolkit.Framework.AssetSystem {
/// Loads the loadable and stores it under the filename given to the loadable.
/// </summary>
/// <param name="name">The loadable to load.</param>
/// <param name="glContext">The OpenGL context to associate with the loaded asset if the loaded asset requires it. May be null, in which the currently active context will be associated with it.</param>
public void Load(string name, GLContext glContext = null) {
public void Load(string name) {
if (glContext == null) glContext = WindowContextsManager.CurrentGL;
string original = name;
string ext = Path.GetExtension(name).ToLower().Substring(1);
@ -110,7 +121,24 @@ namespace SlatedGameToolkit.Framework.AssetSystem {
name = DefaultPathModifier(name);
}
if (!Loaders.ContainsKey(ext)) throw new FrameworkUsageException(string.Format("Failed to find associated loader for file \"{0}\" with extension \"{1}\".", name, ext));
assets[original] = Loaders[ext](name, glContext);
uninitializedDataDictionary[original] = Loaders[ext](name);
uninitializedDataBag.Add(name);
GameEngine.ToRunQueue.Enqueue(InitializeDataRandom);
}
private void InitializeDataRandom() {
string name;
if (uninitializedDataBag.TryTake(out name)) {
if (this.uninitializedDataDictionary.ContainsKey(name)) {
this.assets[name] = this.uninitializedDataDictionary[name].InitializeUseable(glContext);
}
} else {
throw new InternalFrameworkException("Difference in queued uninitialized data loading quantity and actual quantity.");
}
}
private void InitializeData(string name) {
this.assets[name] = this.uninitializedDataDictionary[name].InitializeUseable(glContext);
}
/// <summary>
@ -122,6 +150,13 @@ namespace SlatedGameToolkit.Framework.AssetSystem {
}
}
public void WaitAndLoadAllQueued() {
LoadQueued();
Monitor.Enter(assetThreadLock);
Monitor.Exit(assetThreadLock);
return;
}
/// <summary>
/// Unloads the asset under the given name.
/// This disposes the asset as well as removes it from the manager.

View File

@ -0,0 +1,14 @@
using SlatedGameToolkit.Framework.Graphics.OpenGL;
namespace SlatedGameToolkit.Framework.AssetSystem
{
public interface IAssetData
{
/// <summary>
/// Initializes the asset data and returns the useable variant of said data.
/// </summary>
/// <param name="GLContext">The Open GL Context to use.</param>
/// <returns>The associated useable asset.</returns>
IAssetUseable InitializeUseable(GLContext GLContext);
}
}

View File

@ -1,4 +1,5 @@
using System.IO;
using SlatedGameToolkit.Framework.AssetSystem.AssetData;
using SlatedGameToolkit.Framework.Graphics.OpenGL;
using SlatedGameToolkit.Framework.Graphics.Textures;
using StbImageSharp;
@ -12,15 +13,14 @@ namespace SlatedGameToolkit.Framework.Loaders
/// Any format supported by StbImage is therefore supported.
/// </summary>
/// <param name="path">The path of the texture to load.</param>
/// <param name="glContext">The OpenGL context to associate the texture with.</param>
/// <returns>A texture.</returns>
public static Texture Load2DTexture(string path, GLContext glContext = null)
/// <returns>The data for the texture.</returns>
public static TextureData Load2DTexture(string path)
{
using (Stream stream = File.OpenRead(path))
{
ImageResult image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
TextureData textureData = new TextureData(image.Width, image.Height, image.Data);
return new Texture(textureData, false, glContext);
return textureData;
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using SDL2;
@ -23,6 +24,8 @@ namespace SlatedGameToolkit.Framework {
private static volatile bool deltaChanged;
private static TimeSpan updateDeltaTime = TimeSpan.FromSeconds(1d/40d), frameDeltaTime = TimeSpan.FromSeconds(1d/60d);
public static ConcurrentQueue<ThreadStart> ToRunQueue = new ConcurrentQueue<ThreadStart>();
/// <summary>
/// The amount of updates per second.
///
@ -185,7 +188,13 @@ namespace SlatedGameToolkit.Framework {
updateTimePassedFromLastRender = TimeSpan.Zero;
timePassedFromLastRender = TimeSpan.Zero;
} else {
Thread.Yield();
ThreadStart runnable;
if (ToRunQueue.TryDequeue(out runnable)) {
runnable();
} else
{
Thread.Yield();
}
}
}
stopped = true;

View File

@ -1,8 +1,9 @@
using System;
using SlatedGameToolkit.Framework.AssetSystem;
namespace SlatedGameToolkit.Framework.Graphics.Textures
{
public interface ITexture : IDisposable
public interface ITexture : IAssetUseable, IDisposable
{
/// <summary>
/// Whether or not this model uses a texture that is single chanelled.

View File

@ -3,6 +3,7 @@ using SlatedGameToolkit.Framework.Graphics.OpenGL;
using SlatedGameToolkit.Framework.Graphics.Render;
using SlatedGameToolkit.Framework.Graphics.Window;
using SlatedGameToolkit.Framework.AssetSystem;
using SlatedGameToolkit.Framework.AssetSystem.AssetData;
namespace SlatedGameToolkit.Framework.Graphics.Textures
{

View File

@ -1,17 +0,0 @@
using SlatedGameToolkit.Framework.AssetSystem;
namespace SlatedGameToolkit.Framework.Graphics.Textures
{
public class TextureData
{
public readonly byte[] data;
public readonly int width;
public readonly int height;
public TextureData(int width, int height, byte[] data) {
this.data = data;
this.width = width;
this.height = height;
}
}
}

View File

@ -16,13 +16,14 @@ namespace SlatedGameToolkit.Tools.CommandSystem
/// The help should be a description of how to properly use this invokable.
/// If an argument is specified, the help should cater to that specific argument.
/// Information in the description does not need to be repeated.
/// Arguments will always be lowercase or null.
/// </summary>
/// <param name="arg">An argument this invokable should describe. May be null, to which should return a string that describes this invokable in general.</param>
/// <returns></returns>
string getUsage(string arg);
/// <summary>
/// Executes this invokable.
/// Executes this invokable. Arguments will always be lowercase.
/// </summary>
/// <param name="interactable">The interactable object.</param>
/// <param name="args">The arguments that followed the invokable.</param>

View File

@ -0,0 +1,33 @@
using SlatedGameToolkit.Tools.CommandSystem;
using SlatedGameToolkit.Tools.CommandSystem.Interaction;
namespace SlatedGameToolkit.Tools.Commands
{
public class ExportCommand : IInvocable
{
public void Dispose()
{
throw new System.NotImplementedException();
}
public bool Execute(IInteractable interactable, string[] args)
{
throw new System.NotImplementedException();
}
public string getDescription()
{
throw new System.NotImplementedException();
}
public string[] GetInvokers()
{
throw new System.NotImplementedException();
}
public string getUsage(string arg)
{
return null;
}
}
}

View File

@ -90,7 +90,25 @@ namespace SlatedGameToolkit.Tools.Commands
public string getUsage(string arg)
{
return "\"playground [start | stop | status | log | debug | framerate <framerate> | updates <update rate>]\"";
if (arg == null) {
return "\"playground [start | stop | status | log | debug | framerate <framerate> | updates <update rate>]\"";
} else if (arg.Equals("start")) {
return "Starts the playground.";
} else if (arg.Equals("stop")) {
return "Stops the playground.";
} else if (arg.Equals("status")) {
return "Retrieves the FPS, update rate, whether or not the playground is running, and if debugging is turned on.";
} else if (arg.Equals("log")) {
return "Toggles whether or not to attach logging to the console.";
} else if (arg.Equals("debug")) {
return "Toggles debugging. If logging is disabled, logging will be enabled.";
} else if (arg.Equals("framerate")) {
return "sets the FPS. Needs to be followed by a numerical value representing the new target FPS.";
} else if (arg.Equals("updates")) {
return "Sets the new update rate. Needs to be followed by a numerical value representing the target update rate.";
} else {
return null;
}
}
public void Dispose()

View File

@ -23,7 +23,14 @@ namespace SlatedGameToolkit.Tools.Commands
builder.AppendJoin(", ", invocable.GetInvokers());
interactable.Tell("Possible aliases: " + builder.ToString());
interactable.Tell("Description: " + invocable.getDescription());
interactable.Tell("Usage: " + invocable.getUsage(args.Length > 0 ? args[0] : null));
if (args.Length > 1) {
string desc = invocable.getUsage(args[1]);
if (desc == null) {
interactable.Tell(string.Format("Unable to find argument \"{0}\" for command \"{1}\". Please type \"help\" for more info.", args[1], args[0]));
} else {
interactable.Tell(args[1] + ": " + desc);
}
}
} else {
interactable.Tell("--- Help ---");
foreach (IInvocable invocable in commandMap)

View File

@ -10,6 +10,7 @@ using SlatedGameToolkit.Framework.Input.Devices;
using SlatedGameToolkit.Framework.StateSystem;
using SlatedGameToolkit.Framework.StateSystem.States;
using SlatedGameToolkit.Framework.Graphics;
using SlatedGameToolkit.Framework.AssetSystem;
namespace SlatedGameToolkit.Tools.Utilities.Playground
{
@ -19,7 +20,7 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
private Camera2D camera;
private MeshBatchRenderer renderer;
private BitmapFont font;
private Texture logoTexture, fillerTexture;
private AssetManager assets;
private RectangleMesh logo, textureTester, untextured;
private string lastPressed;
@ -37,8 +38,7 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
public void Deinitialize()
{
logoTexture.Dispose();
fillerTexture.Dispose();
assets.UnloadAll();
font.Dispose();
renderer.Dispose();
window.Dispose();
@ -56,17 +56,21 @@ namespace SlatedGameToolkit.Tools.Utilities.Playground
float ratio = drawableDimensions.Y / drawableDimensions.X;
camera = new Camera2D(2f, ratio * 2f);
renderer = new MeshBatchRenderer(camera, BatchVertexSize: 2048);
assets = new AssetManager();
assets.Loaders["png"] = TextureLoader.Load2DTexture;
logoTexture = TextureLoader.Load2DTexture("Resources/Playground/yhdnbgnc.png");
logo = new RectangleMesh(logoTexture, Color.White);
assets.QueueLoad("Resources/Playground/yhdnbgnc.png");
assets.QueueLoad("Resources/Playground/filler.png");
assets.WaitAndLoadAllQueued();
logo = new RectangleMesh((ITexture)assets["Resources/Playground/yhdnbgnc.png"], Color.White);
logo.Width = 0.5f;
logo.Height = 0.5f;
logo.X = -0.25f;
logo.Y = -0.25f;
fillerTexture = TextureLoader.Load2DTexture("Resources/Playground/filler.png");
textureTester = new RectangleMesh(fillerTexture, Color.White);
textureTester = new RectangleMesh((ITexture)assets["Resources/Playground/filler.png"], Color.White);
textureTester.Width = 0.15f;
textureTester.Height = 0.15f;
textureTester.X = 0.25f;