Skip to content

Conversation

@Arc-huangjingtong
Copy link
Contributor

PR Details

[FastList] is already obsolete ,but still has some code in project ,In the long run, someone has to do this

Related Issue

#2332

Types of changes

  • Docs change / refactoring / dependency upgrade
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My change requires a change to the documentation.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have built and run the editor to try this change out.

@Arc-huangjingtong Arc-huangjingtong changed the title Discard FastList Completely WIP : refactor : Discard FastList Completely May 25, 2025
@Arc-huangjingtong
Copy link
Contributor Author

If this change is approved, I will replace all the fastlists as much as possible :)

@Eideren
Copy link
Collaborator

Eideren commented May 25, 2025

Small note, the idea would be to replace all codepaths referencing the internal array directly with the CollectionsMarshal.AsSpan(List) to avoid performance degradation.
So, instead of

for (int i = 0; i < keyFrames.Count; ++i)
{
   if (parentNodeIndex == 0)
       keyFrames.Items[i].Value -= PivotPosition;
   keyFrames.Items[i].Value *= ScaleImport;
}

You would do

var span = CollectionsMarshal.AsSpan(keyFrames);
for (int i = 0; i < span.Count; ++i)
{
   if (parentNodeIndex == 0)
       span[i].Value -= PivotPosition;
   span[i].Value *= ScaleImport;
}

@Arc-huangjingtong
Copy link
Contributor Author

Arc-huangjingtong commented May 25, 2025

Small note, the idea would be to replace all codepaths referencing the internal array directly with the CollectionsMarshal.AsSpan(List) to avoid performance degradation.CollectionsMarshal.AsSpan(List)So, instead of

for (int i = 0; i < keyFrames.Count; ++i)
{
   if (parentNodeIndex == 0)
       keyFrames.Items[i].Value -= PivotPosition;
   keyFrames.Items[i].Value *= ScaleImport;
}

You would do

var span = CollectionsMarshal.AsSpan(keyFrames);
for (int i = 0; i < span.Count; ++i)
{
   if (parentNodeIndex == 0)
       span[i].Value -= PivotPosition;
   span[i].Value *= ScaleImport;
}

thanks , i see ,i will change


public class AnimationCurveEvaluatorDirectFloatGroup : AnimationCurveEvaluatorDirectBlittableGroupBase<float>
{
protected unsafe override void ProcessChannel(ref Channel channel, CompressedTimeSpan newTime, IntPtr location)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

BTW, I Adjusted the location with unsafe and unsafe

Sort(collection.Items, 0, collection.Count, comparer);
}

public static void Sort<T>(FastList<T> collection, IComparer<T> comparer)
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 delete this, because the method is 0 used , and if deleted , the scripts will easier to maintain , if not it will cause misunderstandings to others

Copy link
Collaborator

Choose a reason for hiding this comment

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

Dispatcher is public, users could be calling this method in their project, so we should leave it for for now and remove it once we remove FastList<T>

Comment on lines 475 to 477
currentRenderTargetsNonMSAA.Clear();
currentRenderTargetsNonMSAA.Capacity = currentRenderTargets.Count;

Copy link
Contributor Author

@Arc-huangjingtong Arc-huangjingtong May 25, 2025

Choose a reason for hiding this comment

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

I think:
.Resize =.Clear() + set Capacity

Copy link
Member

@Kryptos-FR Kryptos-FR May 25, 2025

Choose a reason for hiding this comment

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

Unfortunately, this case isn't equivalent. In FastList<T> if the new capacity was smaller than the previous one, only its size was modified (i.e. no allocations). In List<T> in all cases (except when it's already the correct size) a new array is allocated and a copy occurs.

This will have impact on performance and was one of the reason FastList<T> was created.

Compare

public void Resize(int newSize, bool fastClear)
{
if (size < newSize)
{
EnsureCapacity(newSize);
}
else if (!fastClear && size > newSize)
{
Array.Clear(Items, newSize, size - newSize);
}
size = newSize;
}

with
https://github.com/dotnet/runtime/blob/2765f1856d7ebcc0ebd9708e7a2322f565a1fc77/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs#L97-L124

Copy link
Member

Choose a reason for hiding this comment

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

List<T>.EnsureCapacity might be better.

@Arc-huangjingtong Arc-huangjingtong marked this pull request as ready for review June 21, 2025 15:15
@Arc-huangjingtong Arc-huangjingtong changed the title WIP : refactor : Discard FastList Completely refactor : Discard FastList Completely Jun 21, 2025
@Arc-huangjingtong
Copy link
Contributor Author

image
Now , all the [FastList] is discard but the [FastListStruct] not deleted,I think its need deleted too, maybe other pr ?

@Eideren
Copy link
Collaborator

Eideren commented Jun 22, 2025

FastListStruct isn't marked as obsolete, I haven't looked at whether it makes sense to replace that one with something better/safer so best leave it off for now and have a conversation in a new Issue about it if you want to look into that.

I should have time to review this next week end if Kryptos doesn't do it by then

@Arc-huangjingtong
Copy link
Contributor Author

Arc-huangjingtong commented Jun 23, 2025

FastListStruct isn't marked as obsolete, I haven't looked at whether it makes sense to replace that one with something better/safer so best leave it off for now and have a conversation in a new Issue about it if you want to look into that.FastListStruct

I should have time to review this next week end if Kryptos doesn't do it by then

Yes , thanks, I maybe try it , the key question is use more modern and better ways to improve performance, rather than just delete code :) 👍

@Arc-huangjingtong
Copy link
Contributor Author

👀

Copy link
Collaborator

@Eideren Eideren left a comment

Choose a reason for hiding this comment

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

Sorry about the wait, pretty good work overall, just a few things to fix and some improvements you could tackle.

Comment on lines 244 to 251
var keyFrames = ((AnimationCurve<Vector3>)curve).KeyFrames;
var keyFramesSpan = CollectionsMarshal.AsSpan(keyFrames);
for (int i = 0; i < keyFrames.Count; ++i)
{
if (parentNodeIndex == 0)
keyFrames.Items[i].Value -= PivotPosition;
keyFrames.Items[i].Value *= ScaleImport;
keyFramesSpan[i].Value -= PivotPosition;
keyFramesSpan[i].Value *= ScaleImport;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment on lines 475 to 477
currentRenderTargetsNonMSAA.Resize(currentRenderTargets.Count, false);
currentRenderTargetsNonMSAA.Clear();
currentRenderTargetsNonMSAA.EnsureCapacity(currentRenderTargets.Count);

Copy link
Collaborator

Choose a reason for hiding this comment

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

This isn't quite the same, right now, the way you have it setup is that you're clearing the whole list and then making sure the list can contain at least currentRenderTargets.Count long.
Resize changes the list's capacity to hold the given amount, but also the list's length, so if it is 5 items long and you pass it 3, it will be 3 items long from then on, but it's capacity will still be 5 or whichever capacity the list had at the time.
And passing false to Resize only affects the list when decreasing its length, so when the list is 5 items in length and is changed to 3 items in length, the 4th and 5th item will be set to null/default.

Comment on lines 843 to 846
currentRenderTargets.Resize(renderTargets.Count, false);
currentRenderTargets.Clear();
currentRenderTargets.EnsureCapacity(renderTargets.Count);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as the other comment

var resourceBindingsSpan = CollectionsMarshal.AsSpan(reflection.ResourceBindings);

// prepare resource bindings used internally
for (int i = 0; i < reflection.ResourceBindings.Count; i++)
Copy link
Collaborator

Choose a reason for hiding this comment

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

See my other comment about about using the span's length directly

Comment on lines 82 to 83
renderTargets.Resize(validatedTargetCount, false);

renderTargets.Clear();
renderTargets.EnsureCapacity(renderTargets.Count);
Copy link
Collaborator

Choose a reason for hiding this comment

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

See my other comment

Comment on lines 576 to +579
if (parameterKeyInfos[i].Key == layoutParameterKeyInfo.Key)
{
processedParameters[i] = true;
newParameterKeyInfos.Items[i] = layoutParameterKeyInfo;
newParameterKeyInfosSpan[i] = layoutParameterKeyInfo;
Copy link
Collaborator

Choose a reason for hiding this comment

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

parameterKeyInfos.Count -> newParameterKeyInfosSpan.Length

Comment on lines 692 to 698
var parameterKeyInfosSpan = CollectionsMarshal.AsSpan(parameterKeyInfos);
// Find existing first
for (int i = 0; i < parameterKeyInfos.Count; ++i)
{
if (parameterKeyInfos.Items[i].Key == parameterKey)
if (parameterKeyInfosSpan[i].Key == parameterKey)
{
return parameterKeyInfos.Items[i].GetObjectAccessor();
return parameterKeyInfosSpan[i].GetObjectAccessor();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as other span length stuff

Comment on lines 636 to 653

private unsafe bool IsTetrahedronAllocated(int index)
private unsafe bool IsTetrahedronAllocated(int index, Span<Tetrahedron> tetrahedronSpan)
{
fixed (Tetrahedron* tetrahedron = &tetrahedralization.Items[index])
fixed (Tetrahedron* tetrahedron = &tetrahedronSpan[index])
{
return tetrahedron->Vertices[0] != -1;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Entire method body can be replaced by return tetrahedronSpan[index].Vertices[0] != -1;

Comment on lines 647 to 661
// Mark it as "unused"
fixed (Tetrahedron* tetrahedron = &tetrahedralization.Items[index])
fixed (Tetrahedron* tetrahedron = &tetrahedronSpan[index])
{
tetrahedron->Vertices[0] = -1;

Copy link
Collaborator

Choose a reason for hiding this comment

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

tetrahedronSpan[index].Vertices[0] = -1;

Comment on lines 595 to 602
{
var tetrahedralizationSpan = CollectionsMarshal.AsSpan(tetrahedralization);
// Check connectivity
fixed (Tetrahedron* tetrahedra = tetrahedralization.Items)
fixed (Tetrahedron* tetrahedra = tetrahedralizationSpan)
{
for (int index = 0; index < tetrahedralization.Count; index++)
{
if (!IsTetrahedronAllocated(index))
if (!IsTetrahedronAllocated(index, tetrahedralizationSpan))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yep, same as the rest

Arc-huangjingtong and others added 5 commits July 14, 2025 02:31
Replaces inefficient Clear() calls and Count-based loops with span-based operations using CollectionsMarshal.AsSpan for better performance. Updates affected code in ForwardRenderer, RenderOutputValidator, ImportModelCommand.Animation, and Effect to use spans for clearing and iterating over lists, and adds an obsolete overload in Dispatcher to guide usage towards array-based sorting.
Refactored BowyerWatsonTetrahedralization to eliminate unsafe code and pointer arithmetic, replacing them with direct access to Span and ref variables. This improves code safety, readability, and maintainability while preserving the original logic and performance.
Replaced Count-based loops with span Length in ParameterCollection for improved performance and correctness. Removed unnecessary Clear() call on SortedRenderNodes in RenderSystem as the collection is properly cleared in Reset().
Copilot AI review requested due to automatic review settings August 28, 2025 12:26

This comment was marked as spam.

@Eideren Eideren merged commit a122c71 into stride3d:master Aug 28, 2025
9 checks passed
@Eideren
Copy link
Collaborator

Eideren commented Aug 28, 2025

Tweaked it a bit before merging, thanks for your work @Arc-huangjingtong

@Eideren Eideren changed the title refactor : Discard FastList Completely refactor: Discard internal use of FastList Aug 28, 2025
@Eideren Eideren added the area-Core Issue of the engine unrelated to other defined areas label Aug 28, 2025
Eideren added a commit to Eideren/xenko that referenced this pull request Sep 14, 2025
Eideren added a commit to Eideren/xenko that referenced this pull request Oct 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-Core Issue of the engine unrelated to other defined areas

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants