Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
bd45f82
Fix Vulkan uses of Utilities.CopyMemory
Ethereal77 Jun 16, 2023
98b85cd
Convert Direct3D 11 from SharpDX to Silk
Ethereal77 Jul 18, 2023
ede8326
Convert D3DCompiler from SharpDX to Silk
Ethereal77 Jul 23, 2023
68b13f0
Convert Direct3D 12 from SharpDX to Silk
Ethereal77 Jul 29, 2023
c78fa06
Convert Stride.VirtualReality from SharpDX to Silk
Ethereal77 Jul 31, 2023
b7357f8
Convert RenderDoc plugin from SharpDX to Silk
Ethereal77 Jul 31, 2023
0ca0586
Fix unneeded old reference to SharpDX
Ethereal77 Jul 31, 2023
dbb6a03
Fix type mismatch in comparison of AdapterUid
Ethereal77 Jul 31, 2023
fea1ac5
Re-expose several constants that are consumed by API-agnostic code
Ethereal77 Jul 31, 2023
cb7b497
Update Stride.Graphics project file and dependencies
Ethereal77 Jul 31, 2023
be169d6
Replace custom pointer types with a generic one for use in collections
Ethereal77 Jul 31, 2023
7136893
Convert Stride.Video from SharpDX to Silk
Ethereal77 Aug 1, 2023
def78fd
Fix an OutOfMemory in adapter outputs enumeration
Ethereal77 Aug 3, 2023
704fd05
Fix missing flags
Ethereal77 Aug 3, 2023
2588425
Fix obsoletion warning
Ethereal77 Aug 3, 2023
a69feff
Fix incorrect GraphicsOutput enumeration and computation of desktop b…
Ethereal77 Aug 3, 2023
1b03965
Fix use of invalid Format
Ethereal77 Aug 3, 2023
fbfc337
Fix several NullReferenceExceptions in Direct3D 11
Ethereal77 Aug 3, 2023
c343803
Fix a NullReferenceException and simplify ShaderCompiler a bit
Ethereal77 Aug 3, 2023
c6bb0a4
Replace custom pointer types with generic ones for Direct3D 12
Ethereal77 Aug 3, 2023
b98d014
Minor fixes for all graphics APIs
Ethereal77 Aug 3, 2023
e07f0df
Fix incorrect flags, initialization, and disposal in Direct3D 11
Ethereal77 Aug 7, 2023
78627ab
Add helpers for unsafe pointers and marshalling
Ethereal77 Aug 6, 2024
5afdf5b
Refactor the shader compiler to use the new unsafe helpers
Ethereal77 Aug 6, 2024
6f0af65
Minor cleanup
Ethereal77 Aug 6, 2024
462bb34
Fix a bug in display mode enumeration
Ethereal77 Nov 11, 2024
d106845
Simplify adapters and outputs enumeration using ComPtrs
Ethereal77 Nov 11, 2024
7ef6246
Simplify the display mode enumeration to reduce allocations
Ethereal77 Nov 11, 2024
b0b4d22
Reimplement object lifetimes to fix orphan objects (GraphicsDevice)
Ethereal77 Nov 11, 2024
f626fdc
Reimplement object lifetimes to fix orphan objects (GraphicsResource)
Ethereal77 Nov 11, 2024
0a0d72d
Reimplement adapter and outputs discovery in Silk
Ethereal77 Nov 15, 2024
1ea3a69
Use sizeof(T) instead of Unsafe.SizeOf<T>()
Ethereal77 Feb 23, 2025
40f6c4c
Add helpers to ease the use of COM pointers and handles
Ethereal77 Feb 23, 2025
f58300a
Update UnsafeUtilities to latest .NET, add `scoped` and `ref readonly`
Ethereal77 Feb 23, 2025
71b08cb
Add span to bytes conversion to UnsafeUtilities
Ethereal77 Feb 23, 2025
5c08d3c
Add value bit-casting to UnsafeUtilities
Ethereal77 Feb 23, 2025
2079d6b
Add the ability to reinterpret a value type as a span of elements (li…
Ethereal77 Feb 23, 2025
f436395
Refactor for better use of COM pointers in Graphics Adapters and Outputs
Ethereal77 Feb 23, 2025
3c668f0
Upgrade Silk.NET dependencies to the latest version
Ethereal77 Feb 24, 2025
1a314ee
Modernize and convert to extensions more of UnsafeUtilities
Ethereal77 Feb 26, 2025
0a8d866
Add the ability of reinterpreting an array as a span of another type
Ethereal77 Feb 26, 2025
328face
Improve memory handling for name strings in DebugHelpers
Ethereal77 Feb 26, 2025
9e13c5d
Add convenience NullComPtr for readability
Ethereal77 Feb 26, 2025
dd8cce0
Refactor Buffer to use ComPtrs for clarity and coherence with other t…
Ethereal77 Feb 26, 2025
cdd3ca5
Documentation pass for Buffer
Ethereal77 Mar 1, 2025
005563e
Refactor Texture to use ComPtrs for clarity and coherence with other …
Ethereal77 Jun 24, 2025
72ae5aa
Documentation pass for Texture
Ethereal77 Jun 24, 2025
7574678
Cleanup and document PixelFormat
Ethereal77 Jun 24, 2025
da33ec6
Expand documentation for Buffer and associated types
Ethereal77 Jun 24, 2025
1968725
Refactor CommandList to use ComPtrs for clarity and coherence with ot…
Ethereal77 Jun 25, 2025
9df16c1
Documentation pass for CommandList
Ethereal77 Jun 25, 2025
2c0e3ca
Refactor GraphicsResource to use ComPtrs
Ethereal77 Jun 25, 2025
1f78921
Documentation pass for GraphicsResource
Ethereal77 Jun 25, 2025
c89b834
Rename some files to better adhere to its contained type
Ethereal77 Jun 25, 2025
5974903
Refactor render state objects to use ComPtrs
Ethereal77 Jun 25, 2025
bfbd06b
Documentation pass for render state objects and enums
Ethereal77 Jun 25, 2025
9fd73fe
Refactor PipelineState and Shader bindings pipeline to use ComPtrs
Ethereal77 Jun 25, 2025
603ba87
Documentation pass for PipelineState
Ethereal77 Jun 25, 2025
506e9e1
Refactor GraphicsPresenter to use ComPtrs
Ethereal77 Jun 26, 2025
43f6d8d
Documentation pass for GraphicsPresenter
Ethereal77 Jun 26, 2025
aa4c550
Rename and expand Direct3DInterop to make it more generic and useful
Ethereal77 Jun 26, 2025
120b5c0
Refactor GraphicsDevice to use ComPtrs
Ethereal77 Jun 26, 2025
7fdcc4e
Documentation pass for GraphicsDevice
Ethereal77 Jun 26, 2025
a0d2dc6
Fix some type mismatches that resulted from changing to use ComPtrs
Ethereal77 Jun 27, 2025
ad2b2b2
Corrections from review
Ethereal77 Jun 29, 2025
a9f143d
More corrections from review
Ethereal77 Jul 1, 2025
2a3f0be
Refactor Buffer (D3D12) to use ComPtrs
Ethereal77 Jul 4, 2025
9489bca
Documentation pass for Buffer (D3D12)
Ethereal77 Jul 4, 2025
809b9f3
Refactor Texture (D3D12) to use ComPtrs
Ethereal77 Jul 4, 2025
f071efe
Documentation pass for Texture (D3D12)
Ethereal77 Jul 4, 2025
48b6a9e
Refactor CommandList (D3D12) to use ComPtrs
Ethereal77 Jul 4, 2025
6b327d6
Documentation pass for CommandList (D3D12)
Ethereal77 Jul 4, 2025
446e9a9
Refactor GraphicsDevice (D3D12) to use ComPtrs
Ethereal77 Jul 4, 2025
cdf223b
Documentation pass for GraphicsDevice (D3D12)
Ethereal77 Jul 4, 2025
326cd36
Small changes to GraphicsResource (D3D12)
Ethereal77 Jul 4, 2025
091cc22
Documentation pass for GraphicsResource (D3D12)
Ethereal77 Jul 4, 2025
7c24427
Minor fixes to issues introduced with last changes
Ethereal77 Jul 4, 2025
3cf087c
Minor refactor to GraphicsOutput (D3D12) to use ComPtr
Ethereal77 Jul 4, 2025
a48768d
Documentation pass for GraphicsOutput (D3D12)
Ethereal77 Jul 4, 2025
e3e2acb
Refactor DescriptorPool and DescriptorSet (D3D12) to use ComPtrs
Ethereal77 Jul 9, 2025
746b4e8
Documentation pass for DescriptorPool and DescriptorSet (D3D12)
Ethereal77 Jul 9, 2025
c4fdfd5
Refactor PipelineState (D3D12) to use ComPtrs
Ethereal77 Jul 10, 2025
c30e84c
Documentation pass for PipelineState (D3D12)
Ethereal77 Jul 10, 2025
53fa971
Refactor SamplerState (D3D12) to use ComPtrs
Ethereal77 Jul 11, 2025
327aebe
Documentation pass for SamplerState (D3D12)
Ethereal77 Jul 11, 2025
ea19859
Refactor QueryPool (D3D12) to use ComPtrs
Ethereal77 Jul 11, 2025
659e9c6
Documentation pass for QueryPool (D3D12)
Ethereal77 Jul 11, 2025
f1bd2af
Add documentation for IndexBufferBinding
Ethereal77 Jul 11, 2025
2996b46
Separate GraphicsException in two
Ethereal77 Jul 11, 2025
25d9344
Small fix in SwapChainGraphicsPresenter for Direct3D 12
Ethereal77 Jul 11, 2025
bdc9fca
Improve and document some of the Stride.Games types related to graphics
Ethereal77 Jul 11, 2025
bbb805b
Refactor ShaderCompiler to use ComPtrs
Ethereal77 Jul 12, 2025
2718ab5
Documentation pass for ShaderCompiler
Ethereal77 Jul 12, 2025
1a4872d
Minor fixes and updates after rebase on 'master'
Ethereal77 Jul 13, 2025
63e182e
Fix and improve some Equals() that would cause a NRE
Ethereal77 Jul 14, 2025
3594b4a
Fix Release on a null COM pointer
Ethereal77 Jul 14, 2025
bae46e1
Fix other APIs so they are up to the latest changes
Ethereal77 Jul 14, 2025
b8efb56
Improve DebugHelpers and add a flag to log name changes
Ethereal77 Jul 15, 2025
7a30f76
Add debug names for Graphics Resources when debugging
Ethereal77 Jul 17, 2025
42f1c1c
Add D3D Info Queue message processing when debugging
Ethereal77 Jul 17, 2025
7fe5f95
Minor refactor of PrimitiveQuad
Ethereal77 Jul 17, 2025
61e7e51
Documentation pass
Ethereal77 Jul 17, 2025
a8cb50a
Fix some resource leaks from misusing reference counting + Dispose
Ethereal77 Jul 19, 2025
2e47ba1
Fix a NRE exception due to using an incorrect loop bound
Ethereal77 Jul 19, 2025
1ebe1fc
Small improvements, documentation and cleanup
Ethereal77 Jul 19, 2025
20c8037
Improve PipelineStateCache to better use COM pointers
Ethereal77 Jul 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sources/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
<PackageVersion Include="SharpDX.MediaFoundation" Version="4.2.0" />
<PackageVersion Include="SharpDX.RawInput" Version="4.2.0" />
<PackageVersion Include="SharpDX.XInput" Version="4.2.0" />
<PackageVersion Include="Silk.NET.Direct3D11" Version="2.22.0" />
<PackageVersion Include="Silk.NET.Direct3D12" Version="2.22.0" />
<PackageVersion Include="Silk.NET.Direct3D.Compilers" Version="2.22.0" />
<PackageVersion Include="Silk.NET.OpenGL" Version="2.22.0" />
<PackageVersion Include="Silk.NET.OpenGLES" Version="2.22.0" />
<PackageVersion Include="Silk.NET.OpenGLES.Extensions.EXT" Version="2.22.0" />
Expand Down
17 changes: 11 additions & 6 deletions sources/core/Stride.Core.Mathematics/Color4.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
// -----------------------------------------------------------------------------
/*
* Copyright (c) 2007-2011 SlimDX Group
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Expand All @@ -34,7 +34,7 @@
namespace Stride.Core.Mathematics;

/// <summary>
/// Represents a color in the form of rgba.
/// A RGBA color value with 32-bit floating-point precision per channel.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: inconsistent comment style, spaces in front. Should we apply that style to all files? At least all the one modified in this PR...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's my fault. Out of habit, I'm used to write XMLdocs that way at my work and my other projects because I think it separates the documentation text from the XML tags and reads better that way. I have used that style in all the docs in this PR.
I can revert the indentation if you want. It will be a commit touching a lot of lines again, however.

Copy link
Member

@Kryptos-FR Kryptos-FR Jun 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I don't mind with the new format. Especially when there are long paragraphs.

If we decide to adopt that style, it would be better to be consistent elsewhere. So while I'm it asking you to change the whole codebase, I think using that same style on all the file currently touched by this PR would be a good start.

Side note: do you do it manually of is there a way to configure that style in Visual Studio?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do it manually. I have a VS plugin to aid me in viewing how the generated docs would look like and it updates as I type (DocPreview).

Usually, when I enter to edit a file, I like to leave it in a better shape than when I found it. In Stride, I take more care to not introduce a ton of changes because the project is not mine, and that is not something for me to decide.
But when I modify a file so substantially that the change is going to be huge, I go and improve it overall.

Also, I like writing good-looking documentation 😁

(btw, I have many more extended documentation for several parts of Stride, all with this style. I'm just not including them in this PR because those parts are not graphics-related)

/// </summary>
[DataContract("Color4")]
[DataStyle(DataStyle.Compact)]
Expand All @@ -44,12 +44,17 @@ public struct Color4 : IEquatable<Color4>, ISpanFormattable
/// <summary>
/// The Black color (0, 0, 0, 1).
/// </summary>
public static readonly Color4 Black = new(0.0f, 0.0f, 0.0f);
public static readonly Color4 Black = new(red: 0, green: 0, blue: 0);

/// <summary>
/// The White color (1, 1, 1, 1).
/// </summary>
public static readonly Color4 White = new(1.0f, 1.0f, 1.0f);
public static readonly Color4 White = new(red: 1, green: 1, blue: 1);

/// <summary>
/// The transparent black color (0, 0, 0, 0).
/// </summary>
public static readonly Color4 TransparentBlack = default;

/// <summary>
/// The red component of the color.
Expand Down
64 changes: 39 additions & 25 deletions sources/core/Stride.Core/ComponentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,85 @@
namespace Stride.Core;

/// <summary>
/// Base class for a framework component.
/// Base class for a framework Component.
/// </summary>
/// <remarks>
/// <para>
/// A <strong>Component</strong> is an object that can have a <see cref="Name"/> and other meta-information
/// (see <see cref="Tags"/>), and that can hold references to other sub-Components that depend on it.
/// </para>
/// <para>
/// Components have reference-counting lifetime management, so calling <see cref="DisposeBase.Dispose"/> does
/// not immediately releases its underlying resources, but instead decreases an internal reference count.
/// When that reference count reaches zero, it then calls <see cref="Destroy"/> automatically, where the
/// underlying resources can then be released safely.
/// </para>
/// <para>
/// When <see cref="Destroy"/> is called, not only the resources associated with the Component itself should
/// be released, but it cascades the releasing of resources to its contained sub-Components.
/// </para>
/// </remarks>
/// <seealso cref="IComponent"/>
/// <seealso cref="IReferencable"/>
/// <seealso cref="ICollectorHolder"/>
[DataContract]
public abstract class ComponentBase : DisposeBase, IComponent, ICollectorHolder
{
private string name;
private ObjectCollector collector;


/// <summary>
/// Initializes a new instance of the <see cref="ComponentBase"/> class.
/// Initializes a new instance of the <see cref="ComponentBase"/> class.
/// </summary>
protected ComponentBase()
: this(null)
{
}
protected ComponentBase() : this(name: null) { }

/// <summary>
/// Initializes a new instance of the <see cref="ComponentBase"/> class.
/// Initializes a new instance of the <see cref="ComponentBase"/> class.
/// </summary>
/// <param name="name">The name attached to this component</param>
/// <param name="name">The name attached to the Component, or <see langword="null"/> to use the type's name.</param>
protected ComponentBase(string? name)
{
collector = new ObjectCollector();
Tags = new PropertyContainer(this);
Name = name ?? GetType().Name;
this.name = name ?? GetType().Name;
}


/// <summary>
/// Gets the attached properties to this component.
/// The properties attached to the Component.
/// </summary>
[DataMemberIgnore] // Do not try to recreate object (preserve Tags.Owner)
public PropertyContainer Tags;

/// <summary>
/// Gets or sets the name of this component.
/// Gets or sets the name of the Component.
/// </summary>
/// <value>
/// The name.
/// The name that identifies the Component. It can be <see langword="null"/> to denote it has no specific name.
/// </value>
[DataMemberIgnore] // By default don't store it, unless derived class are overriding this member
[DataMemberIgnore] // By default, don't store it, unless derived class are overriding this member
public virtual string Name
{
get
{
return name;
}
get => name;
set
{
if (value == name) return;
if (value == name)
return;

name = value;

OnNameChanged();
}
}

/// <summary>
/// Disposes of object resources.
/// </summary>
/// <inheritdoc/>
protected override void Destroy()
{
collector.Dispose();
}

/// <inheritdoc/>
ObjectCollector ICollectorHolder.Collector
{
get
Expand All @@ -78,12 +93,11 @@ ObjectCollector ICollectorHolder.Collector
}

/// <summary>
/// Called when <see cref="Name"/> property was changed.
/// Called when the <see cref="Name"/> property has changed.
/// </summary>
protected virtual void OnNameChanged()
{
}
protected virtual void OnNameChanged() { }

/// <inheritdoc/>
public override string ToString()
{
return $"{GetType().Name}: {name}";
Expand Down
136 changes: 81 additions & 55 deletions sources/core/Stride.Core/ComponentBaseExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

using Stride.Core.Annotations;

namespace Stride.Core;

/// <summary>
Expand All @@ -11,92 +9,109 @@ namespace Stride.Core;
public static class ComponentBaseExtensions
{
/// <summary>
/// Keeps a disposable object alive by adding it to a container.
/// Keeps a disposable object alive by adding it to a container.
/// </summary>
/// <typeparam name="T">A component</typeparam>
/// <param name="thisArg">The component to keep alive.</param>
/// <param name="container">The container that will keep a reference to the component.</param>
/// <returns>The same component instance</returns>
public static T? DisposeBy<T>(this T thisArg, ICollectorHolder container)
/// <typeparam name="T">The type of the Component.</typeparam>
/// <param name="component">The Component to keep alive.</param>
/// <param name="container">The container that will keep a reference to the Component.</param>
/// <returns>The same Component instance.</returns>
public static T? DisposeBy<T>(this T component, ICollectorHolder container)
where T : IDisposable
{
if (ReferenceEquals(thisArg, null))
if (component is null)
return default;
return container.Collector.Add(thisArg);

return container.Collector.Add(component);
}

/// <summary>
/// Removes a disposable object that was being kept alive from a container.
/// Removes a disposable object from a container that keeping it alive.
/// </summary>
/// <typeparam name="T">A component</typeparam>
/// <param name="thisArg">The component to remove.</param>
/// <param name="container">The container that kept a reference to the component.</param>
public static void RemoveDisposeBy<T>(this T thisArg, ICollectorHolder container)
/// <typeparam name="T">The type of the Component.</typeparam>
/// <param name="component">The Component to remove.</param>
/// <param name="container">The container that kept a reference to the Component.</param>
public static void RemoveDisposeBy<T>(this T component, ICollectorHolder container)
where T : IDisposable
{
if (ReferenceEquals(thisArg, null))
if (component is null)
return;
container.Collector.Remove(thisArg);

container.Collector.Remove(component);
}

/// <summary>
/// Keeps a referencable object alive by adding it to a container.
/// Keeps a referenceable object alive by adding it to a container.
/// </summary>
/// <typeparam name="T">A component</typeparam>
/// <param name="thisArg">The component to keep alive.</param>
/// <param name="container">The container that will keep a reference to the component.</param>
/// <returns>The same component instance</returns>
public static T? ReleaseBy<T>(this T thisArg, ICollectorHolder container)
/// <typeparam name="T">The type of the Component.</typeparam>
/// <param name="component">The Component to keep alive.</param>
/// <param name="container">The container that will keep a reference to the Component.</param>
/// <returns>The same Component instance.</returns>
public static T? ReleaseBy<T>(this T component, ICollectorHolder container)
where T : IReferencable
{
if (ReferenceEquals(thisArg, null))
if (component is null)
return default;
return container.Collector.Add(thisArg);

return container.Collector.Add(component);
}

/// <summary>
/// Removes a referencable object that was being kept alive from a container.
/// Removes a referenceable object from a container that keeping it alive.
/// </summary>
/// <typeparam name="T">A component</typeparam>
/// <param name="thisArg">The component to remove.</param>
/// <param name="container">The container that kept a reference to the component.</param>
public static void RemoveReleaseBy<T>(this T thisArg, ICollectorHolder container)
/// <typeparam name="T">The type of the Component.</typeparam>
/// <param name="component">The Component to remove.</param>
/// <param name="container">The container that kept a reference to the Component.</param>
public static void RemoveReleaseBy<T>(this T component, ICollectorHolder container)
where T : IReferencable
{
if (ReferenceEquals(thisArg, null))
if (component is null)
return;
container.Collector.Remove(thisArg);

container.Collector.Remove(component);
}

/// <summary>
/// Pins this component as a new reference.
/// Pins this component as a new reference.
/// </summary>
/// <typeparam name="T">A component</typeparam>
/// <param name="thisArg">The component to add a reference to.</param>
/// <returns>This component.</returns>
/// <remarks>This method is equivalent to call <see cref="IReferencable.AddReference"/> and return this instance.</remarks>
public static T? KeepReference<T>(this T thisArg)
/// <typeparam name="T">The type of the Component.</typeparam>
/// <param name="component">The Component to add a reference to.</param>
/// <returns>The same Component instance.</returns>
/// <remarks>
/// This method is equivalent to calling <see cref="IReferencable.AddReference"/> and returning the
/// same <paramref name="component"/> instance.
/// </remarks>
public static T? KeepReference<T>(this T component)
where T : IReferencable
{
if (ReferenceEquals(thisArg, null))
if (component is null)
return default;
thisArg.AddReference();
return thisArg;

component.AddReference();
return component;
}

/// <summary>
/// Pushes a tag to a component and restore it after using it. See remarks for usage.
/// Sets a tag in a Component and removes it after using it.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="component">The component.</param>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <returns>PropertyTagRestore&lt;T&gt;.</returns>
/// <typeparam name="T">The type of the tag.</typeparam>
/// <param name="component">The Component to set a tag for.</param>
/// <param name="key">The key to identify the tag to set.</param>
/// <param name="value">The value of the tag.</param>
/// <returns>
/// A <see cref="PropertyTagRestore{T}"/> structure that can be used with a <see langword="using"/> clause (or
/// manually disposed) to set a tag, perform some action, and remove the tag once finished.
/// </returns>
/// <remarks>
/// This method is used to set save a property value from <see cref="ComponentBase.Tags"/>, set a new value
/// and restore it after. The returned object must be disposed once the original value must be restored.
/// <para>
/// This method is used to set a property value in <see cref="ComponentBase.Tags"/>, perform some actions, and restore
/// the previous value after finishing.
/// </para>
/// <para>
/// The returned object must be disposed once the original value must be restored. It can be done manually
/// (by calling <see cref="PropertyTagRestore{T}.Dispose"/>) or automatically with a <see langword="using"/> clause.
/// </para>
/// </remarks>
public static PropertyTagRestore<T> PushTagAndRestore<T>(this ComponentBase component, PropertyKey<T> key, T value)
public static PropertyTagRestore<T> PushTagAndRestore<T>(this ComponentBase component, PropertyKey<T?> key, T value)
{
// TODO: Not fully satisfied with the name and the extension point (on ComponentBase). We need to review this a bit more
var restorer = new PropertyTagRestore<T>(component, key);
Expand All @@ -105,27 +120,38 @@ public static PropertyTagRestore<T> PushTagAndRestore<T>(this ComponentBase comp
}

/// <summary>
/// Struct PropertyTagRestore
/// A structure returned by <see cref="PushTagAndRestore{T}(ComponentBase, PropertyKey{T}, T)"/> that saves the value of a
/// tag property of an object and, once finished, can restore its value to the previous one.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="T">The type of the tag.</typeparam>
public readonly struct PropertyTagRestore<T> : IDisposable
{
private readonly ComponentBase container;

private readonly PropertyKey<T> key;
private readonly PropertyKey<T?> key;

private readonly T previousValue;
private readonly T? previousValue;

public PropertyTagRestore(ComponentBase container, PropertyKey<T> key)

/// <summary>
/// Initializes a new instance of the <see cref="PropertyTagRestore{T}"/> structure.
/// </summary>
/// <param name="container">The Component that contains the tag to set and restore.</param>
/// <param name="key">The key that identifies the tag to set and restore.</param>
/// <exception cref="ArgumentNullException"><paramref name="container"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
public PropertyTagRestore(ComponentBase container, PropertyKey<T?> key)
: this()
{
ArgumentNullException.ThrowIfNull(container);
ArgumentNullException.ThrowIfNull(key);

this.container = container;
this.key = key;
previousValue = container.Tags.Get(key);
}

/// <inheritdoc/>
public readonly void Dispose()
{
// Restore the value
Expand Down
Loading