-
-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Digi
committed
Oct 10, 2024
1 parent
af57552
commit 7f2f494
Showing
47 changed files
with
704 additions
and
590 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using LevelImposter.Core; | ||
|
||
namespace LevelImposter.AssetLoader; | ||
|
||
public readonly struct LoadableAudio(string _id, IStreamable _streamable) : ICachable | ||
{ | ||
public string ID => _id; | ||
public IStreamable Streamable => _streamable; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
using System; | ||
using LevelImposter.Core; | ||
using UnityEngine; | ||
|
||
namespace LevelImposter.AssetLoader; | ||
|
||
public class AudioLoader : AsyncQueue<LoadableAudio, AudioClip> | ||
{ | ||
private AudioLoader() | ||
{ | ||
} | ||
|
||
public static AudioLoader Instance { get; } = new(); | ||
|
||
/// <summary> | ||
/// Simplified shorthand to load an audioclip asynchronously. | ||
/// </summary> | ||
/// <param name="id">ID for cache</param> | ||
/// <param name="streamable">Streamable with raw image data</param> | ||
/// <param name="callback">Callback when the AudioClip is loaded</param> | ||
public static void LoadAsync(string id, IStreamable streamable, Action<AudioClip> callback) | ||
{ | ||
Instance.AddToQueue( | ||
new LoadableAudio(id, streamable), | ||
callback | ||
); | ||
} | ||
|
||
protected override AudioClip Load(LoadableAudio loadable) | ||
{ | ||
// Open the stream | ||
var stream = loadable.Streamable.OpenStream(); | ||
|
||
// Load the sprite | ||
var loadedFile = WAVLoader.Load(stream, loadable.ID); | ||
|
||
// Close the stream | ||
stream.Close(); | ||
|
||
// Return the loaded sprite | ||
return loadedFile; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using System.IO; | ||
using LevelImposter.Core; | ||
using UnityEngine; | ||
|
||
namespace LevelImposter.AssetLoader; | ||
|
||
public class GIFLoader | ||
{ | ||
/// <summary> | ||
/// Loads a GIF image from a stream. | ||
/// </summary> | ||
/// <param name="imgStream">Image stream to load from</param> | ||
/// <param name="options">Options to apply</param> | ||
/// <returns>A fully-loaded GIFFile containing the image data</returns> | ||
public static LoadedGIF Load( | ||
Stream imgStream, | ||
LoadableSprite loadable) | ||
{ | ||
// Create new file | ||
var gifFile = new GIFFile(loadable.ID); | ||
GCHandler.Register(gifFile); | ||
|
||
// Append Options | ||
var options = loadable.Options; | ||
gifFile.SetPivot(options.Pivot); | ||
// TODO: Allow pixel art in GIFs | ||
|
||
// Load the GIF file from the stream | ||
gifFile.Load(imgStream); | ||
|
||
// Return the GIF file | ||
return new LoadedGIF(gifFile.GetFrameSprite(0), gifFile); | ||
} | ||
|
||
public class LoadedGIF(Sprite _sprite, GIFFile _gifFile) : LoadedSprite(_sprite) | ||
{ | ||
public GIFFile GIFFile => _gifFile; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
using System.IO; | ||
using Il2CppInterop.Runtime.Attributes; | ||
using LevelImposter.Core; | ||
using UnityEngine; | ||
|
||
namespace LevelImposter.AssetLoader; | ||
|
||
public class PNGLoader | ||
{ | ||
/// <summary> | ||
/// Loads a PNG/JPG image from a stream. | ||
/// </summary> | ||
/// <param name="imgStream">Raw PNG/JPG file stream</param> | ||
/// <param name="loadable">Sprite options to apply</param> | ||
/// <returns>A still UnityEngine.Sprite containing the image data</returns> | ||
/// <exception cref="IOException">If the Stream fails to read image data</exception> | ||
public static LoadedSprite Load(Stream imgStream, LoadableSprite loadable) | ||
{ | ||
// Get All Data | ||
var imageDataBuffer = new byte[imgStream.Length]; | ||
var readBytes = imgStream.Read(imageDataBuffer, 0, imageDataBuffer.Length); | ||
if (readBytes != imageDataBuffer.Length) | ||
throw new IOException("Failed to read all image data"); | ||
|
||
// Get Options | ||
var options = loadable.Options; | ||
|
||
// Create Texture | ||
var sprite = ImageDataToSprite( | ||
imageDataBuffer, | ||
loadable.ID, | ||
options.Pivot, | ||
options.PixelArt | ||
); | ||
|
||
// Register in GC | ||
GCHandler.Register(sprite); | ||
|
||
// Create Loaded Sprite | ||
return new LoadedSprite(sprite); | ||
} | ||
|
||
/// <summary> | ||
/// Converts raw PNG/JPG bytes to a still sprite. | ||
/// <para> | ||
/// This is a relatively expensive operation and must be done on the main Unity thread. | ||
/// Texture data is removed from CPU memory making the resulting Sprite non-readable. | ||
/// </para> | ||
/// </summary> | ||
/// <param name="imgData">Raw PNG/JPG data in within IL2CPP memory</param> | ||
/// <param name="name">Name of the resulting sprite objects</param> | ||
/// <param name="pivot">Pivots the sprite by a Vector2. (Default: 0.5f, 0.5f)</param> | ||
/// <param name="isPixelArt">Whether the image is pixel art or not. Disables Bilinear filtering. (Default: false)</param> | ||
/// <returns>A Unity Sprite containing the resulting image data</returns> | ||
[HideFromIl2Cpp] | ||
public static Sprite ImageDataToSprite( | ||
byte[] imgData, | ||
string name = "CustomSprite", | ||
Vector2? pivot = null, | ||
bool isPixelArt = false) | ||
{ | ||
// Generate Texture | ||
Texture2D texture = new(1, 1, TextureFormat.RGBA32, false) | ||
{ | ||
name = $"{name}_tex", | ||
wrapMode = TextureWrapMode.Clamp, | ||
filterMode = isPixelArt ? FilterMode.Point : FilterMode.Bilinear, | ||
hideFlags = HideFlags.HideAndDontSave, | ||
requestedMipmapLevel = 0 | ||
}; | ||
texture.LoadImage(imgData); | ||
|
||
// Remove from CPU Memory | ||
texture.Apply(false, true); | ||
|
||
// Generate Sprite | ||
var sprite = Sprite.Create( | ||
texture, | ||
new Rect(0, 0, texture.width, texture.height), | ||
pivot ?? new Vector2(0.5f, 0.5f), | ||
100.0f, | ||
0, | ||
SpriteMeshType.FullRect | ||
); | ||
|
||
// Set Sprite Flags | ||
sprite.name = $"{name}_sprite"; | ||
sprite.hideFlags = HideFlags.DontUnloadUnusedAsset; | ||
|
||
return sprite; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using System.IO; | ||
using LevelImposter.Core; | ||
using UnityEngine; | ||
|
||
namespace LevelImposter.AssetLoader; | ||
|
||
public class WAVLoader | ||
{ | ||
/// <summary> | ||
/// Loads a WAV from a sound data object. | ||
/// </summary> | ||
/// <param name="soundData">Sound Data to load</param> | ||
/// <returns>Sound data in the form of a Unity AudioClip</returns> | ||
public static AudioClip? Load(LISound? soundData) | ||
{ | ||
// Get Sound Data | ||
if (soundData == null) | ||
return null; | ||
|
||
// Get Asset DB | ||
var mapAssetDB = LIShipStatus.GetInstanceOrNull()?.CurrentMap?.mapAssetDB; | ||
|
||
// Get Sound Stream | ||
var soundDBElem = mapAssetDB?.Get(soundData.dataID); | ||
if (soundDBElem == null) | ||
return null; | ||
|
||
// Get Sound Data | ||
var stream = soundDBElem.OpenStream(); | ||
var audioClip = Load(stream, soundData.id.ToString()); | ||
stream.Close(); | ||
|
||
return audioClip; | ||
} | ||
|
||
/// <summary> | ||
/// Loads a WAV from a raw stream. | ||
/// </summary> | ||
/// <param name="stream">Stream to loads from</param> | ||
/// <param name="name">Name of the resulting object</param> | ||
/// <returns>Sound data in the form of a Unity AudioClip</returns> | ||
public static AudioClip Load(Stream stream, string name) | ||
{ | ||
// Create a new WAV file | ||
var wavFile = new WAVFile(name); | ||
GCHandler.Register(wavFile); | ||
|
||
// Load the WAV file from the stream | ||
wavFile.Load(stream); | ||
|
||
// Return the WAV file | ||
return wavFile.GetClip(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using LevelImposter.Core; | ||
using Reactor.Utilities; | ||
|
||
namespace LevelImposter.AssetLoader; | ||
|
||
/// <summary> | ||
/// A queue that asynchronously loads items in the background using a coroutine. | ||
/// </summary> | ||
/// <typeparam name="TInput">Type used for input values (must be ICachable)</typeparam> | ||
/// <typeparam name="TOutput">Type used for output values (must be ICachable)</typeparam> | ||
public abstract class AsyncQueue<TInput, TOutput> where TInput : ICachable | ||
{ | ||
private IEnumerator? _consumeQueueCoroutine; | ||
|
||
public int QueueSize => Queue.Count; | ||
public int CacheSize => Cache.Count; | ||
protected Queue<QueuedItem> Queue { get; } = new(); | ||
protected ItemCache<TOutput> Cache { get; } = new(); | ||
|
||
/// <summary> | ||
/// Adds an item to the queue. | ||
/// </summary> | ||
/// <param name="inputData">Input data needed to load the item</param> | ||
/// <param name="onLoad">Called when the item is loaded in</param> | ||
public void AddToQueue(TInput inputData, Action<TOutput> onLoad) | ||
{ | ||
// Add the item to the queue | ||
Queue.Enqueue(new QueuedItem(inputData, onLoad)); | ||
|
||
// Start consuming the queue if it's not already running | ||
_consumeQueueCoroutine ??= Coroutines.Start(CoConsumeQueue()); | ||
} | ||
|
||
/// <summary> | ||
/// Clears the queue and cache. | ||
/// </summary> | ||
public void Clear() | ||
{ | ||
Queue.Clear(); | ||
Cache.Clear(); | ||
} | ||
|
||
/// <summary> | ||
/// Called when an item is loaded. | ||
/// </summary> | ||
/// <param name="inputData">Input data used to load item</param> | ||
/// <returns>Output data of the item</returns> | ||
protected abstract TOutput Load(TInput inputData); | ||
|
||
/// <summary> | ||
/// Unity coroutine for consuming the queue. | ||
/// </summary> | ||
private IEnumerator CoConsumeQueue() | ||
{ | ||
// Repeat until the queue is empty | ||
while (Queue.Count > 0) | ||
{ | ||
// Wait for the next available frame | ||
yield return null; | ||
|
||
// Continuously load items until the lag limit is reached | ||
while (LagLimiter.ShouldContinue(20) && Queue.Count > 0) | ||
{ | ||
// Get the next item in the queue | ||
var queuedItem = Queue.Dequeue(); | ||
|
||
// Check if item is in cache | ||
var output = Cache.Get(queuedItem.ID); | ||
if (output == null) | ||
{ | ||
// Load the item | ||
output = Load(queuedItem.InputData); | ||
|
||
// Add the item to the cache | ||
Cache.Add(queuedItem.ID, output); | ||
} | ||
|
||
// Call the onLoad callback | ||
queuedItem.OnLoad(output); | ||
} | ||
} | ||
|
||
// Clear the coroutine | ||
_consumeQueueCoroutine = null; | ||
} | ||
|
||
/// <summary> | ||
/// Represents an item in the queue. | ||
/// </summary> | ||
/// <param name="inputData">Data used for input</param> | ||
/// <param name="onLoad">Callback when the data is loaded in</param> | ||
protected readonly struct QueuedItem(TInput inputData, Action<TOutput> onLoad) | ||
{ | ||
public string ID => InputData.ID; | ||
public TInput InputData { get; } = inputData; | ||
public Action<TOutput> OnLoad { get; } = onLoad; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace LevelImposter.AssetLoader; | ||
|
||
public interface ICachable | ||
{ | ||
public string ID { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace LevelImposter.AssetLoader; | ||
|
||
public class ItemCache<T> | ||
{ | ||
private readonly Dictionary<string, T> _cachedItems = new(); | ||
public int Count => _cachedItems.Count; | ||
|
||
public void Add(string id, T asset) | ||
{ | ||
_cachedItems[id] = asset; | ||
} | ||
|
||
public T? Get(string id) | ||
{ | ||
return _cachedItems.GetValueOrDefault(id); | ||
} | ||
|
||
public void Clear() | ||
{ | ||
_cachedItems.Clear(); | ||
} | ||
} |
Oops, something went wrong.