Fix extension loading and add image stream open #1
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ReFuel.Gltf.Extensions;
|
||||||
|
|
||||||
|
struct KhrMaterialsEmissiveStrengthExtension
|
||||||
|
{
|
||||||
|
public double EmissiveStrenght;
|
||||||
|
|
||||||
|
public KhrMaterialsEmissiveStrengthExtension(double emissiveStrenght)
|
||||||
|
{
|
||||||
|
EmissiveStrenght = emissiveStrenght;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class KhrMaterialsEmissiveStrengthExtensionProvider : IGltfExtensionProvider
|
||||||
|
{
|
||||||
|
public string Name => "KHR_materials_emissive_strength";
|
||||||
|
|
||||||
|
public object ParseExtensionObject(GltfObjectKind parent, JsonElement element)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNotEqual(parent, GltfObjectKind.Material);
|
||||||
|
if (!element.TryGetProperty("emissiveStrength", out var property) || !property.TryGetDouble(out double d))
|
||||||
|
{
|
||||||
|
d = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new KhrMaterialsEmissiveStrengthExtension(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteExtensionObject(object obj, Utf8JsonWriter writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using ReFuel.Gltf.IO;
|
||||||
|
using System;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace ReFuel.Gltf
|
namespace ReFuel.Gltf
|
||||||
@@ -26,6 +27,11 @@ namespace ReFuel.Gltf
|
|||||||
buffer = new GltfSmartId<GltfBuffer>(document.Buffers);
|
buffer = new GltfSmartId<GltfBuffer>(document.Buffers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Stream Open()
|
||||||
|
{
|
||||||
|
return ((StreamWrapper)buffer.Value.Open()).ForkAbs(ByteOffset, ByteOffset + ByteLength);
|
||||||
|
}
|
||||||
|
|
||||||
internal override void Deserialize(JsonElement element)
|
internal override void Deserialize(JsonElement element)
|
||||||
{
|
{
|
||||||
AssertObject(element);
|
AssertObject(element);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace ReFuel.Gltf
|
|||||||
this.extensionMode = extensionMode;
|
this.extensionMode = extensionMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Parse(JsonElement collection)
|
public void Parse(GltfObjectKind parent, JsonElement collection)
|
||||||
{
|
{
|
||||||
if (collection.ValueKind != JsonValueKind.Object)
|
if (collection.ValueKind != JsonValueKind.Object)
|
||||||
throw new Exception("Expected a JSON object.");
|
throw new Exception("Expected a JSON object.");
|
||||||
@@ -25,7 +25,7 @@ namespace ReFuel.Gltf
|
|||||||
{
|
{
|
||||||
if (extensionMode && GltfDocument.TryGetExtensionProvider(property.Name, out IGltfExtensionProvider? provider))
|
if (extensionMode && GltfDocument.TryGetExtensionProvider(property.Name, out IGltfExtensionProvider? provider))
|
||||||
{
|
{
|
||||||
Add(property.Name, provider.ParseExtensionObject(property.Value));
|
Add(property.Name, provider.ParseExtensionObject(parent, property.Value));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ namespace ReFuel.Gltf
|
|||||||
{
|
{
|
||||||
case JsonValueKind.Array:
|
case JsonValueKind.Array:
|
||||||
GltfList list = new GltfList();
|
GltfList list = new GltfList();
|
||||||
list.Parse(property.Value);
|
list.Parse(parent, property.Value);
|
||||||
Add(property.Name, list);
|
Add(property.Name, list);
|
||||||
break;
|
break;
|
||||||
case JsonValueKind.False:
|
case JsonValueKind.False:
|
||||||
@@ -58,7 +58,7 @@ namespace ReFuel.Gltf
|
|||||||
break;
|
break;
|
||||||
case JsonValueKind.Object:
|
case JsonValueKind.Object:
|
||||||
GltfCollection child = new GltfCollection(extensionMode);
|
GltfCollection child = new GltfCollection(extensionMode);
|
||||||
child.Parse(property.Value);
|
child.Parse(parent, property.Value);
|
||||||
Add(property.Name, child);
|
Add(property.Name, child);
|
||||||
break;
|
break;
|
||||||
case JsonValueKind.True:
|
case JsonValueKind.True:
|
||||||
@@ -79,7 +79,7 @@ namespace ReFuel.Gltf
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Parse(JsonElement collection)
|
public void Parse(GltfObjectKind parent, JsonElement collection)
|
||||||
{
|
{
|
||||||
if (collection.ValueKind != JsonValueKind.Array)
|
if (collection.ValueKind != JsonValueKind.Array)
|
||||||
throw new Exception("Expected a JSON array.");
|
throw new Exception("Expected a JSON array.");
|
||||||
@@ -90,7 +90,7 @@ namespace ReFuel.Gltf
|
|||||||
{
|
{
|
||||||
case JsonValueKind.Array:
|
case JsonValueKind.Array:
|
||||||
GltfList list = new GltfList();
|
GltfList list = new GltfList();
|
||||||
list.Parse(element);
|
list.Parse(parent, element);
|
||||||
Add(list);
|
Add(list);
|
||||||
break;
|
break;
|
||||||
case JsonValueKind.False:
|
case JsonValueKind.False:
|
||||||
@@ -115,7 +115,7 @@ namespace ReFuel.Gltf
|
|||||||
break;
|
break;
|
||||||
case JsonValueKind.Object:
|
case JsonValueKind.Object:
|
||||||
GltfCollection child = new GltfCollection(false);
|
GltfCollection child = new GltfCollection(false);
|
||||||
child.Parse(element);
|
child.Parse(parent, element);
|
||||||
Add(child);
|
Add(child);
|
||||||
break;
|
break;
|
||||||
case JsonValueKind.True:
|
case JsonValueKind.True:
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ namespace ReFuel.Gltf
|
|||||||
throw new Exception("File does not have a valid GLB header.");
|
throw new Exception("File does not have a valid GLB header.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GltfDocument OpenBinary(StreamWrapper str, in GlbHeader header)
|
private static GltfDocument OpenBinary(StreamWrapper fileStream, in GlbHeader header)
|
||||||
{
|
{
|
||||||
if (header.Version < 2)
|
if (header.Version < 2)
|
||||||
{
|
{
|
||||||
@@ -142,24 +142,23 @@ namespace ReFuel.Gltf
|
|||||||
|
|
||||||
JsonDocument? document = null;
|
JsonDocument? document = null;
|
||||||
List<StreamWrapper> binaryStreams = new List<StreamWrapper>();
|
List<StreamWrapper> binaryStreams = new List<StreamWrapper>();
|
||||||
|
while (GlbChunk.Read(fileStream, out GlbChunk chunk))
|
||||||
while (GlbChunk.Read(str, out GlbChunk chunk))
|
|
||||||
{
|
{
|
||||||
using StreamWrapper substream = str.ForkAbs(str.Position, str.Position + chunk.Size, true);
|
using StreamWrapper chunkStream = fileStream.ForkAbs(fileStream.Position, fileStream.Position + chunk.Size, true);
|
||||||
|
|
||||||
if (chunk.Type == GlbChunkType.Json)
|
if (chunk.Type == GlbChunkType.Json)
|
||||||
{
|
{
|
||||||
document = JsonDocument.Parse(substream.Fork(keepOpen:true));
|
document = JsonDocument.Parse(chunkStream);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Stream copy = new MemoryStream();
|
Stream copy = new MemoryStream();
|
||||||
|
//TODO: is this reasonable?
|
||||||
substream.CopyTo(copy);
|
chunkStream.CopyTo(copy);
|
||||||
copy.Position = 0;
|
copy.Position = 0;
|
||||||
|
|
||||||
binaryStreams.Add(new StreamWrapper(copy));
|
binaryStreams.Add(new StreamWrapper(copy));
|
||||||
}
|
}
|
||||||
|
fileStream.Seek(chunk.Size, SeekOrigin.Current);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document == null)
|
if (document == null)
|
||||||
@@ -294,8 +293,8 @@ namespace ReFuel.Gltf
|
|||||||
{
|
{
|
||||||
switch (name)
|
switch (name)
|
||||||
{
|
{
|
||||||
case "extension": Extension.Parse(element); break;
|
case "extension": Extension.Parse(Kind, element); break;
|
||||||
case "extra": Extras.Parse(element); break;
|
case "extra": Extras.Parse(Kind, element); break;
|
||||||
case "scenes":
|
case "scenes":
|
||||||
AssertArray(element, "scenes");
|
AssertArray(element, "scenes");
|
||||||
foreach (JsonElement child in element.EnumerateArray())
|
foreach (JsonElement child in element.EnumerateArray())
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace ReFuel.Gltf
|
|||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object ParseExtensionObject(JsonElement element)
|
public object ParseExtensionObject(GltfObjectKind parent, JsonElement element)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace ReFuel.Gltf
|
|||||||
{
|
{
|
||||||
if (Uri == null)
|
if (Uri == null)
|
||||||
{
|
{
|
||||||
throw new Exception("Only images with a URI property are to be opened.");
|
return BufferView!.Open();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Uri.IsAbsoluteUri)
|
if (Uri.IsAbsoluteUri)
|
||||||
@@ -64,9 +64,13 @@ namespace ReFuel.Gltf
|
|||||||
// otherwise
|
// otherwise
|
||||||
|
|
||||||
if (provider != null)
|
if (provider != null)
|
||||||
|
{
|
||||||
return provider.OpenFile(pwd, Uri) ?? throw new FileNotFoundException(Uri.ToString());
|
return provider.OpenFile(pwd, Uri) ?? throw new FileNotFoundException(Uri.ToString());
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
return File.OpenRead(Path.Combine(pwd ?? string.Empty, Uri.LocalPath));
|
return File.OpenRead(Path.Combine(pwd ?? string.Empty, Uri.LocalPath));
|
||||||
|
}
|
||||||
|
|
||||||
// throw new Exception("Unrecognized URI scheme.");
|
// throw new Exception("Unrecognized URI scheme.");
|
||||||
}
|
}
|
||||||
@@ -74,7 +78,9 @@ namespace ReFuel.Gltf
|
|||||||
internal override void Deserialize(JsonElement element)
|
internal override void Deserialize(JsonElement element)
|
||||||
{
|
{
|
||||||
if (element.ValueKind != JsonValueKind.Object)
|
if (element.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
throw new Exception("Expected a JSON object for glTF image node.");
|
throw new Exception("Expected a JSON object for glTF image node.");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (JsonProperty property in element.EnumerateObject())
|
foreach (JsonProperty property in element.EnumerateObject())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -165,8 +165,14 @@ namespace ReFuel.Gltf
|
|||||||
this.material.Id = index;
|
this.material.Id = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: materials.
|
if (element.TryGetProperty("targets", out JsonElement targetsElement))
|
||||||
// TODO: morph targets.
|
{
|
||||||
|
foreach (JsonProperty property in materialElement.EnumerateObject())
|
||||||
|
{
|
||||||
|
//TODO: targets
|
||||||
|
//this.Targets.Add(weights);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (element.TryGetProperty("mode", out JsonElement modeElement))
|
if (element.TryGetProperty("mode", out JsonElement modeElement))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace ReFuel.Gltf
|
|||||||
{
|
{
|
||||||
public abstract GltfObjectKind Kind { get; }
|
public abstract GltfObjectKind Kind { get; }
|
||||||
public GltfDocument Document { get; protected set; }
|
public GltfDocument Document { get; protected set; }
|
||||||
public GltfCollection Extension { get; protected set; } = new GltfCollection();
|
public GltfCollection Extension { get; protected set; } = new GltfCollection(true);
|
||||||
public GltfCollection Extras { get; protected set; } = new GltfCollection();
|
public GltfCollection Extras { get; protected set; } = new GltfCollection();
|
||||||
|
|
||||||
protected GltfObject()
|
protected GltfObject()
|
||||||
@@ -47,11 +47,11 @@ namespace ReFuel.Gltf
|
|||||||
{
|
{
|
||||||
if (element.TryGetProperty("extras", out JsonElement extras))
|
if (element.TryGetProperty("extras", out JsonElement extras))
|
||||||
{
|
{
|
||||||
Extras.Parse(extras);
|
Extras.Parse(Kind, extras);
|
||||||
}
|
}
|
||||||
if (element.TryGetProperty("extension", out JsonElement extension))
|
if (element.TryGetProperty("extensions", out JsonElement extension))
|
||||||
{
|
{
|
||||||
Extension.Parse(extension);
|
Extension.Parse(Kind, extension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ using System.Text.Json.Serialization;
|
|||||||
|
|
||||||
namespace ReFuel.Gltf
|
namespace ReFuel.Gltf
|
||||||
{
|
{
|
||||||
public class GltfSerializedExtensionProvider<T> : IGltfExtensionProvider<T>
|
public class GltfSerializedExtensionProvider<T> : IGltfExtensionProvider
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
public Type? DataType { get; } = typeof(T);
|
|
||||||
|
|
||||||
public GltfSerializedExtensionProvider(string name)
|
public GltfSerializedExtensionProvider(string name)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
@@ -18,14 +16,14 @@ namespace ReFuel.Gltf
|
|||||||
throw new Exception("This type is not JSON serializable.");
|
throw new Exception("This type is not JSON serializable.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public T ParseExtension(JsonElement element)
|
public T ParseExtension(GltfObjectKind parent, JsonElement element)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Deserialize<T>(element) ?? throw new Exception("Could not deserialize extension object.");
|
return JsonSerializer.Deserialize<T>(element) ?? throw new Exception("Could not deserialize extension object.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public object ParseExtensionObject(JsonElement element)
|
public object ParseExtensionObject(GltfObjectKind parent, JsonElement element)
|
||||||
{
|
{
|
||||||
return ParseExtension(element)!;
|
return ParseExtension(parent, element)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteExtension(T value, Utf8JsonWriter writer)
|
public void WriteExtension(T value, Utf8JsonWriter writer)
|
||||||
|
|||||||
@@ -6,15 +6,8 @@ namespace ReFuel.Gltf
|
|||||||
public interface IGltfExtensionProvider
|
public interface IGltfExtensionProvider
|
||||||
{
|
{
|
||||||
string Name { get; }
|
string Name { get; }
|
||||||
Type? DataType { get; }
|
|
||||||
|
|
||||||
object ParseExtensionObject(JsonElement element);
|
object ParseExtensionObject(GltfObjectKind parent, JsonElement element);
|
||||||
void WriteExtensionObject(object obj, Utf8JsonWriter writer);
|
void WriteExtensionObject(object obj, Utf8JsonWriter writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IGltfExtensionProvider<T> : IGltfExtensionProvider
|
|
||||||
{
|
|
||||||
T ParseExtension(JsonElement element);
|
|
||||||
void WriteExtension(T value, Utf8JsonWriter writer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+255
-136
@@ -4,213 +4,323 @@ namespace ReFuel.Gltf.IO
|
|||||||
{
|
{
|
||||||
public class StreamWrapper : Stream
|
public class StreamWrapper : Stream
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly Stream _baseStream;
|
||||||
/// The private synchronized proxy for the stream.
|
|
||||||
/// </summary>
|
|
||||||
private readonly Stream _proxy;
|
|
||||||
|
|
||||||
/// <summary>
|
// Shared across forks so that concurrent access to the same base stream is serialized.
|
||||||
/// Head pointer for stream r/w operations.
|
private readonly object _sync;
|
||||||
/// </summary>
|
|
||||||
public long Head { get; private init; } = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
// Logical position relative to Head.
|
||||||
/// Tail pointer for stream r/w operations. -1 signifies unbound.
|
private long _position;
|
||||||
/// </summary>
|
|
||||||
public long Tail { get; private init; } = -1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The number of bytes in the stream if there is a limit set.
|
|
||||||
/// </summary>
|
|
||||||
public long Limit
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (Tail <= 0)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CanWrite)
|
|
||||||
{
|
|
||||||
return Tail - Head;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the limit size or however many bytes are available.
|
|
||||||
return Math.Min(Tail - Head, _proxy.Length - Head);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public long Head { get; private init; }
|
||||||
|
public long Tail { get; private set; } = -1; // -1 = unbound
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The base stream for this wrapper.
|
/// The base stream for this wrapper.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Stream BaseStream { get; }
|
public Stream BaseStream => _baseStream;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the stream has been set to be read only.
|
/// True if the stream has been set to be read only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsReadOnly { get; private init; } = false;
|
public bool IsReadOnly { get; private init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the base stream should be kept open.
|
/// True if the base stream should be kept open.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool KeepOpen { get; }
|
public bool KeepOpen { get; }
|
||||||
|
|
||||||
public override bool CanRead => _proxy.CanRead;
|
public long Limit
|
||||||
public override bool CanSeek => _proxy.CanSeek;
|
{
|
||||||
public override bool CanWrite => !IsReadOnly || _proxy.CanWrite;
|
get
|
||||||
public override long Length => Tail < 0 ? _proxy.Length - Head : Math.Min(Tail - Head, _proxy.Length - Head);
|
{
|
||||||
|
if (Tail < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long end = Math.Min(Tail, _baseStream.Length);
|
||||||
|
return Math.Max(0, end - Head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead => _baseStream.CanRead;
|
||||||
|
public override bool CanSeek => _baseStream.CanSeek;
|
||||||
|
public override bool CanWrite => !IsReadOnly && _baseStream.CanWrite;
|
||||||
|
|
||||||
|
public override long Length
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
long end = Tail >= 0 ? Math.Min(Tail, _baseStream.Length) : _baseStream.Length;
|
||||||
|
return Math.Max(0, end - Head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override long Position
|
public override long Position
|
||||||
{
|
{
|
||||||
get => ToRelOffset(_proxy.Position);
|
get { lock (_sync)
|
||||||
set => _proxy.Position = ToAbsOffset(value, true);
|
{
|
||||||
|
return _position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set { lock (_sync)
|
||||||
|
{
|
||||||
|
Seek(value, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamWrapper(Stream baseStream, bool keepOpen = false, bool isReadOnly = false)
|
public StreamWrapper(Stream baseStream, bool keepOpen = false, bool isReadOnly = false)
|
||||||
{
|
{
|
||||||
BaseStream = baseStream;
|
_baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
|
||||||
KeepOpen = keepOpen;
|
KeepOpen = keepOpen;
|
||||||
IsReadOnly = isReadOnly;
|
IsReadOnly = isReadOnly;
|
||||||
|
_sync = new object();
|
||||||
_proxy = Synchronized(baseStream);
|
Head = 0;
|
||||||
|
Tail = -1;
|
||||||
|
_position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Flush() => _proxy.Flush();
|
private StreamWrapper(Stream baseStream, object sync,
|
||||||
|
long head, long tail,
|
||||||
|
bool keepOpen, bool isReadOnly)
|
||||||
|
{
|
||||||
|
_baseStream = baseStream;
|
||||||
|
_sync = sync;
|
||||||
|
Head = head;
|
||||||
|
Tail = tail;
|
||||||
|
KeepOpen = keepOpen;
|
||||||
|
IsReadOnly = isReadOnly;
|
||||||
|
_position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_baseStream.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
int clamped = ClampReadLength();
|
ValidateBufferArgs(buffer, offset, count);
|
||||||
int rc =_proxy.Read(buffer, offset, clamped);
|
|
||||||
Debug.Assert(Tail < 0 || _proxy.Position <= Tail);
|
|
||||||
return rc;
|
|
||||||
|
|
||||||
int ClampReadLength()
|
lock (_sync)
|
||||||
{
|
{
|
||||||
if (Tail > 0)
|
int clamped = ClampReadLength(count);
|
||||||
|
if (clamped == 0)
|
||||||
{
|
{
|
||||||
return Math.Min(count, (int)Math.Min(int.MaxValue, Math.Max(0, Tail - _proxy.Position)));
|
return 0;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
_baseStream.Seek(Head + _position, SeekOrigin.Begin);
|
||||||
|
int rc = _baseStream.Read(buffer, offset, clamped);
|
||||||
|
_position += rc;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(Span<byte> buffer)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
int clamped = ClampReadLength(buffer.Length);
|
||||||
|
if (clamped == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_baseStream.Seek(Head + _position, SeekOrigin.Begin);
|
||||||
|
int rc = _baseStream.Read(buffer.Slice(0, clamped));
|
||||||
|
_position += rc;
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int ReadByte()
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
if (ClampReadLength(1) == 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_baseStream.Seek(Head + _position, SeekOrigin.Begin);
|
||||||
|
int b = _baseStream.ReadByte();
|
||||||
|
if (b >= 0)
|
||||||
|
{
|
||||||
|
_position++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ClampReadLength(int count)
|
||||||
|
{
|
||||||
|
if (Tail < 0)
|
||||||
{
|
{
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
long remaining = Tail - (Head + _position);
|
||||||
|
if (remaining <= 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (int)Math.Min(count, remaining);
|
||||||
|
}
|
||||||
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
{
|
{
|
||||||
long absOffset = 0;
|
lock (_sync)
|
||||||
|
{
|
||||||
|
long newRel;
|
||||||
switch (origin)
|
switch (origin)
|
||||||
{
|
{
|
||||||
case SeekOrigin.Begin:
|
case SeekOrigin.Begin:
|
||||||
absOffset = ToAbsOffset(offset, true);
|
newRel = offset;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SeekOrigin.Current:
|
||||||
|
newRel = _position + offset;
|
||||||
|
break;
|
||||||
|
|
||||||
case SeekOrigin.End:
|
case SeekOrigin.End:
|
||||||
if (Tail < 0)
|
long windowEndRel = Tail >= 0
|
||||||
{
|
? Tail - Head
|
||||||
try
|
: _baseStream.Length - Head;
|
||||||
{
|
newRel = windowEndRel + offset;
|
||||||
return ToRelOffset(_proxy.Seek(offset, origin), true);
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(origin));
|
||||||
}
|
}
|
||||||
catch (ArgumentOutOfRangeException)
|
|
||||||
|
if (newRel < 0)
|
||||||
{
|
{
|
||||||
// Set the stream back to a known state.
|
throw new IOException("Cannot seek before the beginning of the stream.");
|
||||||
_proxy.Seek(Tail, SeekOrigin.Begin);
|
}
|
||||||
throw;
|
|
||||||
|
long max = Length;
|
||||||
|
if (Tail >= 0 && newRel > max)
|
||||||
|
{
|
||||||
|
throw new IOException("Cannot seek past the end of the bounded stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_position = newRel;
|
||||||
|
return _position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
absOffset = Math.Max(Head, Math.Min(Tail, Tail + offset));
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
break;
|
{
|
||||||
case SeekOrigin.Current:
|
ValidateBufferArgs(buffer, offset, count);
|
||||||
absOffset = Math.Max(Head, Math.Min(_proxy.Position + offset, Tail));
|
|
||||||
break;
|
if (IsReadOnly)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("This stream is read only.");
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
if (Tail >= 0 && Head + _position + count > Tail)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Write would overrun the stream limit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_baseStream.Seek(Head + _position, SeekOrigin.Begin);
|
||||||
|
_baseStream.Write(buffer, offset, count);
|
||||||
|
_position += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(ReadOnlySpan<byte> buffer)
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("This stream is read only.");
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
if (Tail >= 0 && Head + _position + buffer.Length > Tail)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Write would overrun the stream limit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_baseStream.Seek(Head + _position, SeekOrigin.Begin);
|
||||||
|
_baseStream.Write(buffer);
|
||||||
|
_position += buffer.Length;
|
||||||
}
|
}
|
||||||
return ToRelOffset(_proxy.Seek(absOffset, SeekOrigin.Begin));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetLength(long value)
|
public override void SetLength(long value)
|
||||||
{
|
{
|
||||||
if (IsReadOnly)
|
if (IsReadOnly)
|
||||||
throw new Exception("This stream is read only.");
|
{
|
||||||
|
throw new NotSupportedException("This stream is read only.");
|
||||||
if (Tail < 0)
|
|
||||||
throw new NotSupportedException("Cannot expand the stream at this point.");
|
|
||||||
|
|
||||||
if (Tail != _proxy.Length)
|
|
||||||
throw new NotSupportedException("Cannot resize the stream at this point.");
|
|
||||||
|
|
||||||
_proxy.SetLength(Head + value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(byte[] buffer, int offset, int count)
|
ArgumentOutOfRangeException.ThrowIfNegative(value);
|
||||||
|
|
||||||
|
lock (_sync)
|
||||||
{
|
{
|
||||||
if (IsReadOnly)
|
long newEnd = Head + value;
|
||||||
throw new Exception("This stream is read only.");
|
|
||||||
|
|
||||||
if (Tail > 0 && _proxy.Position + count >= Tail)
|
if (Tail >= 0 && newEnd > Tail)
|
||||||
throw new Exception("Source array will overrun the current limit.");
|
{
|
||||||
|
throw new NotSupportedException("Cannot grow the stream past the tail bound.");
|
||||||
_proxy.Write(buffer, offset, count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamWrapper Fork(long head = 0, long tail = -1, bool keepOpen = false, bool isReadOnly = false)
|
_baseStream.SetLength(newEnd);
|
||||||
|
|
||||||
|
Tail = Tail >= 0 ? Tail : -1;
|
||||||
|
|
||||||
|
if (_position > value)
|
||||||
{
|
{
|
||||||
return ForkAbs(ToAbsOffset(head, true), tail < 0 ? Tail : ToAbsOffset(tail, true), keepOpen, isReadOnly);
|
_position = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamWrapper ForkAbs(long head = 0, long tail = -1, bool keepOpen = false, bool isReadOnly = false)
|
|
||||||
{
|
|
||||||
if (head < 0 || head > BaseStream.Length)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(head), "Head pointer out of range.");
|
|
||||||
|
|
||||||
if (tail >= 0 && tail > BaseStream.Length)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(tail), "Tail pointer is too large for the given offset.");
|
|
||||||
|
|
||||||
return new StreamWrapper(BaseStream, keepOpen)
|
|
||||||
{
|
|
||||||
Head = head,
|
|
||||||
Tail = tail,
|
|
||||||
IsReadOnly = IsReadOnly || isReadOnly,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private long ToAbsOffset(long relOffset, bool safe = false)
|
|
||||||
{
|
|
||||||
if (relOffset < 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(relOffset), relOffset, "The relative offset cannot be less than zero.");
|
|
||||||
|
|
||||||
if (Limit >= 0 && relOffset > Limit)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(relOffset), relOffset, "The relative offset cannot be greater than the limit.");
|
|
||||||
|
|
||||||
long value = relOffset + Head;
|
|
||||||
|
|
||||||
if (safe)
|
|
||||||
{
|
|
||||||
if ((Tail > 0 && value >= Tail) || value < Head)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(relOffset));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
public StreamWrapper Fork(long head = 0, long tail = -1,
|
||||||
|
bool keepOpen = false, bool isReadOnly = false)
|
||||||
|
{
|
||||||
|
long absHead = Head + head;
|
||||||
|
long absTail = tail < 0 ? Tail : Head + tail;
|
||||||
|
return ForkAbs(absHead, absTail, keepOpen, isReadOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long ToRelOffset(long absOffset, bool safe = false)
|
public StreamWrapper ForkAbs(long head = 0, long tail = -1,
|
||||||
|
bool keepOpen = false, bool isReadOnly = false)
|
||||||
{
|
{
|
||||||
long value = absOffset - Head;
|
if (head < 0 || head > _baseStream.Length)
|
||||||
|
|
||||||
if (safe)
|
|
||||||
{
|
{
|
||||||
if ((Tail > 0 && value > Length) || value < 0)
|
throw new ArgumentOutOfRangeException(nameof(head));
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(absOffset));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
if (tail >= 0 && tail > _baseStream.Length)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(tail));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tail >= 0 && tail < head)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(tail));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StreamWrapper(_baseStream, _sync, head, tail,
|
||||||
|
keepOpen, IsReadOnly || isReadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateBufferArgs(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(buffer);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(offset);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(count);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(count, buffer.Length - offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
@@ -218,10 +328,19 @@ namespace ReFuel.Gltf.IO
|
|||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
|
|
||||||
if (!disposing)
|
if (!disposing)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!KeepOpen)
|
if (KeepOpen)
|
||||||
_proxy.Dispose();
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_baseStream.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.0.1</Version>
|
<Version>0.0.1</Version>
|
||||||
|
|||||||
Reference in New Issue
Block a user