2024-11-18

Good article going into the details of NAT-traversal

Found this excellent article that describes in detail how NAT-traversal work.

I hadn't consider the 'virtual' aspect of how we open up communication channels. It all makes sense, good thing this article was written in such a great way!

 https://tailscale.com/blog/how-nat-traversal-works

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-11-01

Is there an issue with this code, is there an issue with me, is there an issue with software?

Scenario: someone asks for feedback on this piece of code where they want to centralize object instantiation to solve the problem. In their mind they use defensive programming to make sure we do not cause errors. The person is driven and likes to communicate.

In a team, we are asked to strive to reach consensus for how to tackle technical challenges and encourage collaboration and open discussions. 

Everyones opinion carries equal weight, in discussion about the review someone on the team says that the nested if-statements makes the code hard to read and suggests that we break them into early returns instead.

Someone read an article about SOLID and suggests that it tries to tackle too many responsibilities and suggests that we break logic into standalone functions with clearer names such as TryToAddDependencyIfParent(...). Everyone on the team seems happy with contributing to making this code great.

I suggest that this code is missing the bigger picture, that we should strive to make object lifetime deterministic. That we need to be clear on errors and what happens when we don't follow the happy path. 

People on the team don't understand.I phrased it wrong, it didn't get enough traction. Someone believes they understand my concern, and suggests we change the function to bool TryCreateEntity(...). The Team is happy of having built a solid solution to the problem as a group and incorporated everyones oppinion into the final solution, we have built something great together.

GO TEAM!


  public void CreateEntity(GameBlueprint blueprint)

{ if (blueprint != null) { if (blueprint.Template is GameObject prefab) { var instance = Object.Instantiate(prefab); if (instance.TryGetComponent(out GameEntity entity)) { if (entity.transform.parent.TryGetComponent(out GameEntity parent)) { _system.AddEntityDependency(parent, entity); } _system.NotifyEntityCreated(entity); } } } }