Skip to content

Conversation

@Akarinnnnn
Copy link
Contributor

Reduced HGlobal allocation times in CallResult scenario by cache IntPtr buffer .

Changed .NET Standard project to make use of new(.NET 5+) API, Unity will also get benefit from it. Compatibility to Framework and old Unity is handled by conditional compilcation.

Changed .NET Standard project to make use of new(.NET 5+) API, Unity will also get benefit from it. Compatibility to Framework and old Unity is handled by conditional compilcation.
Leaked HGlobal is allocated by member initialization statement, removed it.
private static IntPtr m_pCallbackMsg;
private static int m_initCount;
// 4096 * 3 is enough for most api calls,
private static int s_currentCallResultBufferSize = 4096 * 3;
Copy link
Owner

Choose a reason for hiding this comment

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

The comment on this scales me a little bit

@rlabrecque
Copy link
Owner

Any objections @JamesMcGhee @yaakov-h ?

@yaakov-h
Copy link

yaakov-h commented Feb 6, 2025

I'm a little bit concerned about thread safety and concurrency re. the static buffer, and also that there is no limit on the buffer until hitting INT_MAX and then crashing, particularly when this buffer stays around for the lifetime of the application.

If use of ArrayPool/ObjectPool is available or similar then it might be better to reuse that system.

Also throwing raw IntPtrs just kind of gives me the heebie-jeebies in general.

@Akarinnnnn
Copy link
Contributor Author

Akarinnnnn commented Feb 6, 2025

I'm a little bit concerned about thread safety and concurrency re. the static buffer, and also that there is no limit on the buffer until hitting INT_MAX and then crashing, particularly when this buffer stays around for the lifetime of the application.

Your concerns is right, but I see some usage of static shared buffers on CallbackMsg_t, so this might be acceptable for now. Buffer shrinking is also a good point, I plan to fix it by a counter about buffer too large. And also, crashing whole application will be fixed next. The exception thrown by reaching INT_MAX is handled and will only skip particular result.

If use of ArrayPool/ObjectPool is available or similar then it might be better to reuse that system.

Considering we still support some clients that don't have native ArrayPool and Span support, if we want to do pooling, we will have to write one ourself, which will add some burden to maintenance I afraid.

@JamesMcGhee
Copy link
Contributor

Overall no objections to the concept not where I can run and test myself but read over the change set.

As to the Thread Safty aspect
Implementing developer's responsibility, a Unity dev should naturally always register on main that said a developer that has worked with Unreal will know that they have callbacks coming in on Steam's own thread so it is on you in each delegate to handle directing to the desired thread i.e. it is an implementing developer's problem.

On the overflow topic, it should have belts and braces to check for and handle overflow, there are a few ways you could implement recovering space it should be a non-issue there aren't that many CallResult calls that should be called so frequently that it would be a problem. That said there should never be an Access Violation or Overlow that is unhandled either not in C# anyway

@Akarinnnnn
Copy link
Contributor Author

Wait, let me do some test first.

@Akarinnnnn
Copy link
Contributor Author

Functional test under .NET 8 and .NET Standard 2.1 passed.

@Akarinnnnn
Copy link
Contributor Author

Functional test code below, call me for making project setup public.

// See https://aka.ms/new-console-template for more information
using Steamworks;

Console.WriteLine("Hello, World!");
CancellationTokenSource loopCts = new();

ulong modFAGE_id = 1579583001;

try
{

	SteamAPI.Init();

	Thread dispatcher = new(() =>
	{
		while (!loopCts.IsCancellationRequested)
			SteamAPI.RunCallbacks();
	});

	dispatcher.IsBackground = true;
	dispatcher.Start();

	// Add test code here
	
	CallResult<SteamUGCQueryCompleted_t>[] completionHolder = [.. GenerateReceivers(10, null!)];

	for (int i = 0; i < completionHolder.Length; i++)
	{
		var queryHandle = SteamUGC.CreateQueryUGCDetailsRequest([new(modFAGE_id + (uint)i)], 1);
		var queryApiCallHandle = SteamUGC.SendQueryUGCRequest(queryHandle);
		completionHolder[i].Set(queryApiCallHandle); 
	}



	SpinWait spinner = new();
	while (Sync.CompletedJobsCount != 10)
	{
		spinner.SpinOnce();
	}
}
finally
{
	loopCts.Cancel();
	SteamAPI.Shutdown();
}

static IEnumerable<CallResult<SteamUGCQueryCompleted_t>> GenerateReceivers(int amount, Barrier? completionBarrier)
{
	// completionBarrier.AddParticipants(amount);

	for (int i = 0; i < amount; i++)
	{
		yield return new((r, f) =>
		{
			if (f)
			{
				Console.WriteLine("API Invocation IO Failure");

				Interlocked.Increment(ref Sync.CompletedJobsCount);
				return;
			}

			var completedHandle = r.m_handle;
			if (!SteamUGC.GetQueryUGCResult(completedHandle, 0, out var details))
			{
				Console.WriteLine("GetResult() generic failure");

                Interlocked.Increment(ref Sync.CompletedJobsCount);
				return;
			}

			if (!SteamUGC.GetQueryUGCStatistic(completedHandle, 0, EItemStatistic.k_EItemStatistic_NumComments, out ulong commentsCount))
			{
                Console.WriteLine("GetStatistic() generic failure");

                Interlocked.Increment(ref Sync.CompletedJobsCount);
				return;
            }

            Console.WriteLine($"Title: {details.m_rgchTitle}, URL: {details.m_rgchURL}, Comments count: {commentsCount}");

            Interlocked.Increment(ref Sync.CompletedJobsCount); 
			return;
        });
	}
}


static class Sync
{
    public static volatile int CompletedJobsCount = 0;
}

@Akarinnnnn Akarinnnnn requested a review from rlabrecque February 7, 2025 15:21
@Akarinnnnn Akarinnnnn force-pushed the improve-dispatcher branch from 82286de to 609733e Compare May 17, 2025 11:02
Introduce `TargetFrameworks` have critial effect to build process, which results breaking batch-build step of nuget pack.

Considering side effect above introduced by adding .NET 8 support, to maintain packageing functions, abandon .NET 8 now.
@Akarinnnnn Akarinnnnn changed the title Reduce HGlobal allocation in CallResult scenario. And take advantages of new .NET API. Reduce HGlobal allocation in CallResult scenario May 17, 2025
In addition, original buggy size rounding is replaced with a more plain implementation.
@Akarinnnnn
Copy link
Contributor Author

I feel this version is more satisfied, ready to review.

@Akarinnnnn
Copy link
Contributor Author

Ping for requesting review @yaakov-h @JamesMcGhee @rlabrecque

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants