Fix extension loading and add image stream open #1

Merged
themixedupstuff merged 6 commits from JoeTwizzle/ReFuel.Gltf:master into master 2026-10-24 15:05:09 +02:00
12 changed files with 359 additions and 195 deletions
@@ -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();
}
}
+7 -1
View File
@@ -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);
+7 -7
View File
@@ -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:
+9 -10
View File
@@ -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())
+1 -1
View File
@@ -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();
} }
+7 -1
View File
@@ -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())
{ {
+8 -2
View File
@@ -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))
{ {
+4 -4
View File
@@ -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)
+1 -8
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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>