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;
|
||||
|
||||
namespace ReFuel.Gltf
|
||||
@@ -26,6 +27,11 @@ namespace ReFuel.Gltf
|
||||
buffer = new GltfSmartId<GltfBuffer>(document.Buffers);
|
||||
}
|
||||
|
||||
public Stream Open()
|
||||
{
|
||||
return ((StreamWrapper)buffer.Value.Open()).ForkAbs(ByteOffset, ByteOffset + ByteLength);
|
||||
}
|
||||
|
||||
internal override void Deserialize(JsonElement element)
|
||||
{
|
||||
AssertObject(element);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace ReFuel.Gltf
|
||||
this.extensionMode = extensionMode;
|
||||
}
|
||||
|
||||
public void Parse(JsonElement collection)
|
||||
public void Parse(GltfObjectKind parent, JsonElement collection)
|
||||
{
|
||||
if (collection.ValueKind != JsonValueKind.Object)
|
||||
throw new Exception("Expected a JSON object.");
|
||||
@@ -25,7 +25,7 @@ namespace ReFuel.Gltf
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace ReFuel.Gltf
|
||||
{
|
||||
case JsonValueKind.Array:
|
||||
GltfList list = new GltfList();
|
||||
list.Parse(property.Value);
|
||||
list.Parse(parent, property.Value);
|
||||
Add(property.Name, list);
|
||||
break;
|
||||
case JsonValueKind.False:
|
||||
@@ -58,7 +58,7 @@ namespace ReFuel.Gltf
|
||||
break;
|
||||
case JsonValueKind.Object:
|
||||
GltfCollection child = new GltfCollection(extensionMode);
|
||||
child.Parse(property.Value);
|
||||
child.Parse(parent, property.Value);
|
||||
Add(property.Name, child);
|
||||
break;
|
||||
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)
|
||||
throw new Exception("Expected a JSON array.");
|
||||
@@ -90,7 +90,7 @@ namespace ReFuel.Gltf
|
||||
{
|
||||
case JsonValueKind.Array:
|
||||
GltfList list = new GltfList();
|
||||
list.Parse(element);
|
||||
list.Parse(parent, element);
|
||||
Add(list);
|
||||
break;
|
||||
case JsonValueKind.False:
|
||||
@@ -115,7 +115,7 @@ namespace ReFuel.Gltf
|
||||
break;
|
||||
case JsonValueKind.Object:
|
||||
GltfCollection child = new GltfCollection(false);
|
||||
child.Parse(element);
|
||||
child.Parse(parent, element);
|
||||
Add(child);
|
||||
break;
|
||||
case JsonValueKind.True:
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace ReFuel.Gltf
|
||||
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)
|
||||
{
|
||||
@@ -142,24 +142,23 @@ namespace ReFuel.Gltf
|
||||
|
||||
JsonDocument? document = null;
|
||||
List<StreamWrapper> binaryStreams = new List<StreamWrapper>();
|
||||
|
||||
while (GlbChunk.Read(str, out GlbChunk chunk))
|
||||
while (GlbChunk.Read(fileStream, 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)
|
||||
{
|
||||
document = JsonDocument.Parse(substream.Fork(keepOpen:true));
|
||||
document = JsonDocument.Parse(chunkStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stream copy = new MemoryStream();
|
||||
|
||||
substream.CopyTo(copy);
|
||||
//TODO: is this reasonable?
|
||||
chunkStream.CopyTo(copy);
|
||||
copy.Position = 0;
|
||||
|
||||
binaryStreams.Add(new StreamWrapper(copy));
|
||||
}
|
||||
fileStream.Seek(chunk.Size, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
if (document == null)
|
||||
@@ -294,8 +293,8 @@ namespace ReFuel.Gltf
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "extension": Extension.Parse(element); break;
|
||||
case "extra": Extras.Parse(element); break;
|
||||
case "extension": Extension.Parse(Kind, element); break;
|
||||
case "extra": Extras.Parse(Kind, element); break;
|
||||
case "scenes":
|
||||
AssertArray(element, "scenes");
|
||||
foreach (JsonElement child in element.EnumerateArray())
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace ReFuel.Gltf
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public object ParseExtensionObject(JsonElement element)
|
||||
public object ParseExtensionObject(GltfObjectKind parent, JsonElement element)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace ReFuel.Gltf
|
||||
{
|
||||
if (Uri == null)
|
||||
{
|
||||
throw new Exception("Only images with a URI property are to be opened.");
|
||||
return BufferView!.Open();
|
||||
}
|
||||
|
||||
if (Uri.IsAbsoluteUri)
|
||||
@@ -64,9 +64,13 @@ namespace ReFuel.Gltf
|
||||
// otherwise
|
||||
|
||||
if (provider != null)
|
||||
{
|
||||
return provider.OpenFile(pwd, Uri) ?? throw new FileNotFoundException(Uri.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
return File.OpenRead(Path.Combine(pwd ?? string.Empty, Uri.LocalPath));
|
||||
}
|
||||
|
||||
// throw new Exception("Unrecognized URI scheme.");
|
||||
}
|
||||
@@ -74,7 +78,9 @@ namespace ReFuel.Gltf
|
||||
internal override void Deserialize(JsonElement element)
|
||||
{
|
||||
if (element.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
throw new Exception("Expected a JSON object for glTF image node.");
|
||||
}
|
||||
|
||||
foreach (JsonProperty property in element.EnumerateObject())
|
||||
{
|
||||
|
||||
@@ -165,8 +165,14 @@ namespace ReFuel.Gltf
|
||||
this.material.Id = index;
|
||||
}
|
||||
|
||||
// TODO: materials.
|
||||
// TODO: morph targets.
|
||||
if (element.TryGetProperty("targets", out JsonElement targetsElement))
|
||||
{
|
||||
foreach (JsonProperty property in materialElement.EnumerateObject())
|
||||
{
|
||||
//TODO: targets
|
||||
//this.Targets.Add(weights);
|
||||
}
|
||||
}
|
||||
|
||||
if (element.TryGetProperty("mode", out JsonElement modeElement))
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace ReFuel.Gltf
|
||||
{
|
||||
public abstract GltfObjectKind Kind { get; }
|
||||
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();
|
||||
|
||||
protected GltfObject()
|
||||
@@ -47,11 +47,11 @@ namespace ReFuel.Gltf
|
||||
{
|
||||
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
|
||||
{
|
||||
public class GltfSerializedExtensionProvider<T> : IGltfExtensionProvider<T>
|
||||
public class GltfSerializedExtensionProvider<T> : IGltfExtensionProvider
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public Type? DataType { get; } = typeof(T);
|
||||
|
||||
public GltfSerializedExtensionProvider(string name)
|
||||
{
|
||||
Name = name;
|
||||
@@ -18,14 +16,14 @@ namespace ReFuel.Gltf
|
||||
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.");
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -6,15 +6,8 @@ namespace ReFuel.Gltf
|
||||
public interface IGltfExtensionProvider
|
||||
{
|
||||
string Name { get; }
|
||||
Type? DataType { get; }
|
||||
|
||||
object ParseExtensionObject(JsonElement element);
|
||||
object ParseExtensionObject(GltfObjectKind parent, JsonElement element);
|
||||
void WriteExtensionObject(object obj, Utf8JsonWriter writer);
|
||||
}
|
||||
|
||||
public interface IGltfExtensionProvider<T> : IGltfExtensionProvider
|
||||
{
|
||||
T ParseExtension(JsonElement element);
|
||||
void WriteExtension(T value, Utf8JsonWriter writer);
|
||||
}
|
||||
}
|
||||
+273
-154
@@ -4,213 +4,323 @@ namespace ReFuel.Gltf.IO
|
||||
{
|
||||
public class StreamWrapper : Stream
|
||||
{
|
||||
/// <summary>
|
||||
/// The private synchronized proxy for the stream.
|
||||
/// </summary>
|
||||
private readonly Stream _proxy;
|
||||
private readonly Stream _baseStream;
|
||||
|
||||
/// <summary>
|
||||
/// Head pointer for stream r/w operations.
|
||||
/// </summary>
|
||||
public long Head { get; private init; } = 0;
|
||||
// Shared across forks so that concurrent access to the same base stream is serialized.
|
||||
private readonly object _sync;
|
||||
|
||||
/// <summary>
|
||||
/// Tail pointer for stream r/w operations. -1 signifies unbound.
|
||||
/// </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);
|
||||
}
|
||||
}
|
||||
// Logical position relative to Head.
|
||||
private long _position;
|
||||
|
||||
public long Head { get; private init; }
|
||||
public long Tail { get; private set; } = -1; // -1 = unbound
|
||||
/// <summary>
|
||||
/// The base stream for this wrapper.
|
||||
/// </summary>
|
||||
public Stream BaseStream { get; }
|
||||
|
||||
public Stream BaseStream => _baseStream;
|
||||
/// <summary>
|
||||
/// True if the stream has been set to be read only.
|
||||
/// </summary>
|
||||
public bool IsReadOnly { get; private init; } = false;
|
||||
|
||||
public bool IsReadOnly { get; private init; }
|
||||
/// <summary>
|
||||
/// True if the base stream should be kept open.
|
||||
/// </summary>
|
||||
public bool KeepOpen { get; }
|
||||
|
||||
public override bool CanRead => _proxy.CanRead;
|
||||
public override bool CanSeek => _proxy.CanSeek;
|
||||
public override bool CanWrite => !IsReadOnly || _proxy.CanWrite;
|
||||
public override long Length => Tail < 0 ? _proxy.Length - Head : Math.Min(Tail - Head, _proxy.Length - Head);
|
||||
public long Limit
|
||||
{
|
||||
get
|
||||
{
|
||||
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
|
||||
{
|
||||
get => ToRelOffset(_proxy.Position);
|
||||
set => _proxy.Position = ToAbsOffset(value, true);
|
||||
get { lock (_sync)
|
||||
{
|
||||
return _position;
|
||||
}
|
||||
}
|
||||
set { lock (_sync)
|
||||
{
|
||||
Seek(value, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public StreamWrapper(Stream baseStream, bool keepOpen = false, bool isReadOnly = false)
|
||||
{
|
||||
BaseStream = baseStream;
|
||||
_baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
|
||||
KeepOpen = keepOpen;
|
||||
IsReadOnly = isReadOnly;
|
||||
|
||||
_proxy = Synchronized(baseStream);
|
||||
_sync = new object();
|
||||
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)
|
||||
{
|
||||
int clamped = ClampReadLength();
|
||||
int rc =_proxy.Read(buffer, offset, clamped);
|
||||
Debug.Assert(Tail < 0 || _proxy.Position <= Tail);
|
||||
return rc;
|
||||
ValidateBufferArgs(buffer, offset, count);
|
||||
|
||||
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)));
|
||||
}
|
||||
else
|
||||
{
|
||||
return count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
long remaining = Tail - (Head + _position);
|
||||
if (remaining <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)Math.Min(count, remaining);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
long absOffset = 0;
|
||||
switch (origin)
|
||||
lock (_sync)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
absOffset = ToAbsOffset(offset, true);
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
if (Tail < 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ToRelOffset(_proxy.Seek(offset, origin), true);
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
// Set the stream back to a known state.
|
||||
_proxy.Seek(Tail, SeekOrigin.Begin);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
long newRel;
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
newRel = offset;
|
||||
break;
|
||||
|
||||
absOffset = Math.Max(Head, Math.Min(Tail, Tail + offset));
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
absOffset = Math.Max(Head, Math.Min(_proxy.Position + offset, Tail));
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
newRel = _position + offset;
|
||||
break;
|
||||
|
||||
case SeekOrigin.End:
|
||||
long windowEndRel = Tail >= 0
|
||||
? Tail - Head
|
||||
: _baseStream.Length - Head;
|
||||
newRel = windowEndRel + offset;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(origin));
|
||||
}
|
||||
|
||||
if (newRel < 0)
|
||||
{
|
||||
throw new IOException("Cannot seek before the beginning of the stream.");
|
||||
}
|
||||
|
||||
long max = Length;
|
||||
if (Tail >= 0 && newRel > max)
|
||||
{
|
||||
throw new IOException("Cannot seek past the end of the bounded stream.");
|
||||
}
|
||||
|
||||
_position = newRel;
|
||||
return _position;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
ValidateBufferArgs(buffer, offset, count);
|
||||
|
||||
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)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
throw new Exception("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)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
throw new Exception("This stream is read only.");
|
||||
|
||||
if (Tail > 0 && _proxy.Position + count >= Tail)
|
||||
throw new Exception("Source array will overrun the current limit.");
|
||||
|
||||
_proxy.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public StreamWrapper Fork(long head = 0, long tail = -1, bool keepOpen = false, bool isReadOnly = false)
|
||||
{
|
||||
return ForkAbs(ToAbsOffset(head, true), tail < 0 ? Tail : ToAbsOffset(tail, true), keepOpen, isReadOnly);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
throw new NotSupportedException("This stream is read only.");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(value);
|
||||
|
||||
private long ToRelOffset(long absOffset, bool safe = false)
|
||||
{
|
||||
long value = absOffset - Head;
|
||||
|
||||
if (safe)
|
||||
lock (_sync)
|
||||
{
|
||||
if ((Tail > 0 && value > Length) || value < 0)
|
||||
long newEnd = Head + value;
|
||||
|
||||
if (Tail >= 0 && newEnd > Tail)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(absOffset));
|
||||
throw new NotSupportedException("Cannot grow the stream past the tail bound.");
|
||||
}
|
||||
|
||||
_baseStream.SetLength(newEnd);
|
||||
|
||||
Tail = Tail >= 0 ? Tail : -1;
|
||||
|
||||
if (_position > value)
|
||||
{
|
||||
_position = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -218,10 +328,19 @@ namespace ReFuel.Gltf.IO
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!KeepOpen)
|
||||
_proxy.Dispose();
|
||||
if (KeepOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
_baseStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>0.0.1</Version>
|
||||
|
||||
Reference in New Issue
Block a user