Visar inlägg med etikett c#. Visa alla inlägg
Visar inlägg med etikett c#. Visa alla inlägg

2024-11-06

Stack allocated BitArray in C#

I've been playing around a bit with trying to get a value typed struct with an 'array like data load' into a good state where I feel happy using it. I'll let this serve as a template for future reference to highlight what the syntax look like.

Beware that using the 'in' parameter comes with a caveat. C# treats all methods as 'maybe mutable' and hence will do a defensive copy on the referenced object prior to invoking the function.

The alternative is to use ref, but then you are cluttering the invocation.

Example usage with static anonymous function to avoid lambda allocation:

hits = new int[3];
var container = new List<int>();
range.ForeachMarkedIndexWithCount(hits, container, static (entityIndex, hits, container, i) =>
{
hits[i] = entityIndex;
container.Add(entityIndex);
});

range.CombineAndMaskWith(combine, mask);


Code-snippet using unsafe struct, with fixed ulong words with constant size etc

 public unsafe struct NetIndexRange {

private const int BitsPerWord = 64;
private const int NumWords = (NetConstants.NumMaxNetEntities + BitsPerWord - 1) / BitsPerWord; // Ceiling of for example : 1000/64 = 16

private fixed ulong _words[NumWords];

private ref ulong ModifyWord(int index)
{
return ref _words[index];
}
public void MarkIndex(int index)
{
int wordIndex = index / 64;
int bitPosition = index % 64;
ulong mask = 1UL << bitPosition;

ModifyWord(wordIndex) |= mask;
}

public void ClearIndex(int index)
{
int wordIndex = index / 64;
int bitPosition = index % 64;
ulong mask = ~(1UL << bitPosition);

ModifyWord(wordIndex) &= mask;
}

public void CombineAndMaskWith(in NetIndexRange combine, in NetIndexRange mask)
{
for (int wordIndex = 0; wordIndex < NumWords; ++wordIndex)
{
ulong combinedWord = _words[wordIndex] | combine._words[wordIndex];
ulong maskWord = mask._words[wordIndex];
ulong result = combinedWord & maskWord;

ModifyWord(wordIndex) = result;
}
}

public void ForeachMarkedIndexWithCount<T, U>(T t, U u, Action<int, T, U, int> eachMarkedIndexAction) 
where T : class
where U : class
{
int count = 0;
int entityIndex = 0;

try
{
for (int wordIndex = 0; wordIndex < NumWords; wordIndex++)
{
ulong word = ModifyWord(wordIndex);
int bitPosition = 0;

while (word != 0)
{
if ((word & 1UL) != 0)
{
entityIndex = (wordIndex * 64) + bitPosition;
eachMarkedIndexAction(entityIndex, t, u, count++);
}
word >>= 1;
bitPosition++;
}
}
}
catch (Exception e)
{
throw new Exception($"Error while invoking action on net_{entityIndex} {Environment.NewLine} OriginalException: {e}");
}
}

2024-10-10

Using Roslyn inside Rider

 Recently I was struggling with actually getting the 'roslyn source code generator' to generate things available to my other project inside Rider.

Storing this as a memo for the exact things that made this work so I have a good baseline to branch out from in the future. Might help someone.

Setup the project

- New Solution
-- Generator (class library, netstandard2.0)
-- MyConsoleApp (console app, netstandard2.0)

ManageNuGetPackages

- Microsoft.CodeAnalysis.Analyzers
- Microsoft.CodeAnalysisCSharp.Workspaces
- Microsoft.Compilers.Toolset
- NetStandard.Library 2.x.x

MyConsoleApp-project

Add a dependency to Generator, and then modify the .csproj file like this

<!-- Add this as a new ItemGroup, replacing paths and names appropriately -->
<ItemGroup>
<ProjectReference Include="..\Generator\Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Net.Compilers.Toolset" Version="4.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

Generator-project

Adding launchsettings.json

Create a file at ./Properties/launchSettings.json and paste the following code inside it (I had to create the Properties-Directory inside Rider)

{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"Generators": {
"commandName": "DebugRoslynComponent",
"targetProject": "../MyConsoleApp/MyConsoleApp.csproj"
}
} } 

Create the Generator-file

Create a file called HelloWorldGenerator.cs and paste this content inside it
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace Generator;

[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{

}

public void Execute(GeneratorExecutionContext context)
{
// begin creating the source we'll inject into the users compilation
var sourceBuilder = new StringBuilder(@"
using System;
namespace HelloWorldGenerated
{
public static class HelloWorld
{
public static void SayHello()
{
Console.WriteLine(""Hello from generated code!"");
Console.WriteLine(""The following syntax trees existed in the compilation that created this program:"");
");

// using the context, get a list of syntax trees in the users compilation
var syntaxTrees = context.Compilation.SyntaxTrees;

// add the filepath of each tree to the class we're building
foreach (SyntaxTree tree in syntaxTrees)
{
sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");");
}

// finish creating the source to inject
sourceBuilder.Append(@"
}
}
}");

// inject the created source into the users compilation
context.AddSource("helloWorldGenerator", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
}
}
End result 
after doing a couple of attempts at
- Reload project
- Restart Rider
- Rebuild Solution
Inside the console app it should now be possible to write
HelloWorldGenerated.HelloWorld.SayHello();
And inside the 'solution view' you should be able to see the following by expanding
/MyConsoleApp/Dependencies/.NetStandard2.0/Source Generators/Generator.HelloWorldGenerator




2013-04-11

Issue with my event solution...

This will be a short update mostly as a note to self on why it is bad to use events or something with a strong connection between classes in a solution with garbage collections.

Basically, I have some home brewn event system based around

  • Singleton Controller
  • Event/Command with id[string]
  • Registering interest on controller with ( id[string], cb[Function/Delegate])
And then I would add connection between classes similar to this:
            Controller.instance.registerInterest("id_1", onSpawnParticles);
            Controller.instance.registerInterest("id_2", onReloadDatabase);

Now, due to the issue of the callback being a function, whenever I want to delete something that registered their interest at Controller I would have to manually remove all connections or else GC will always find connections to that class...

I probably should have done this solution with the help of Observer-pattern so I only get one callback and thus only have to do one "unregister" call for each class that falls out of scope... Much easier :o)

Ah well, good thing to learn the hard way :)