c# – Unit Of Work with multiple database context

It is unclear what your motive is for using two contexts, and if/how you expect these to behave in conjunction with one another. The solutions are fairly straightforward, but which solution would apply to your case is not clear. So I’ve tried to address your possible concerns here.

  1. When the update to main context fails, should the update to the secondary context automatically fail? And vice versa?

If that is the case, then you need these contexts to work under a shared transaction scope. In the comments, Ewan already provided you with a link that showcases how to do this.

  1. Do the entities that are in both contexts always have the same structure? If one changes, will the other need to be changed the same way?

Because if that is not the case, then you should not be reusing the same entity classes for these two contexts. Just because two things are currently the same doesn’t mean that they invariably always will be.

And when they won’t, then they should be separated from the get go. You’re really going to regret having to separate them after you’ve developed your codebase.

  1. Are both contexts assumed to be up to date with one another?

In your example, do you need to actively account for the possibility of finding an entity in one context and not finding it in the other? Because your code currently blindly assumes that you’re going to find both. Unless you have a very good (and so far unmentioned) reason to rely on that, this seems like a bad approach.

  1. Do both contexts have the exact same entity tables? Will this always be the case?

If you’ve answered “yes” to both questions 2 and 4, then you can abstract a reusable interface that both contexts implement, which I’ll call IBaseContext for now.

public interface IBaseContext
{
    DbSet<Product> Products { get; set; }

    int SaveChanges();
}

Note: this interface can contain SaveChanges, which will “happen” to match the method from DbContext. The compiler allows this, as long as all implementations of IBaseContext have such a int SaveChanges() available. Regardless of whether they inherit it from DbContext or have defined it for themselves.

This opens the door to reusably using your db contexts. This is very relevant for your question:

What if the databases are more than 2 ?

When the contexts have a reusable interface, that means you can write reusable logic that can handle any context that implements this reusable interface.

This means you could, for example, opt for a generic unit of work:

public interface IUnitOfWork : IDisposable
{
    IProductsRepository ProductsRepository { get; }
    int Complete();
}

public class UnitOfWork<TContext> : IUnitOfWork where TContext : IBaseContext
{
    public UnitOfWork(TContext context, IProductsRepository productsRepository)
    {
        _context = context;

        ProductsRepository = productsRepository;
        ProductsRepository.Context = context;
    }
 
    private TContext _context;

    public IProductsRepository ProductsRepository { get; private set; }

    public int Complete()
    {
        return _context.SaveChanges();
    }
}

This unit of work will work for any implementation of IBaseContext, provided that the context has been registered in your service provider.

Note that this slightly complicates the repository DI logic. You can’t rely on DI by itself to figure out which context you want to inject when. But you can change your repositories to allow their context being set after the fact by their unit of work.

There are other ways of doing the same thing, that may be cleaner in your opinion, but it will get incrementally more complicated depending on how clean you want it. I just used the easiest to read example here.

If you cannot create a reusable interface on your context types, then what you’re currently doing is what you’re going to have to do. When you can’t do it reusably, you have to write each case manually.

c# – SmartUnit: Unit Testing with DI

I was thinking about how unit tests require you to manually instantiate all your dependencies, even though many of them are the same for every test or don’t really matter (such as logging, where people usually just inject mocks). As a possible solution to this problem, I wrote a unit test framework that injects any known dependencies into the class or method, or null if a dependency is not known. It also supports theory-type tests with nested methods. Here are some quick examples before we dive into the code:

Declaring a test

(Assertion)
public void MyTest() {}

Configuring dependencies

public interface IBar {}
public class Bar : IBar {}
public class Foo
{
   public Foo(IBar bar) {}
}

public class AssertionConfiguration : AssertionSet
{
   public override void Configure()
   {
       this.AddSingleton<Foo>();
       this.AddSingleton<IBar, Bar>();
   }
}

Using the AssertionSet attribute

This goes on either a class, where it applies to all tests, or on a method, where it overrides any assertion set declared on the class.

(AssertionSet(typeof(AssertionConfiguration)))
(Assertion)
public void MyTest(Foo foo, IBar bar) {}

Theory-type tests

public void MyTest((Callback) Action action)
{
    action();

    (Assertion)
    public void Foo() {}

    (Assertion)
    public void Bar() {}
}

Skipping a test

Apply the Skip attribute to the test method.

(Skip(Reason = "Doesn't work because ..."))

Assertions

A successful test is considered to be one that completes without exceptions, so while these assertion extension methods are provided, any assertion library will work.

obj.AssertThat<MyType>(o => o == 1);
obj.AssertThatAsync<MyType>(async o => (await o.GetSomething()) == 1);
obj.AssertException<MyType, Exception>(o => o.ThrowException());
obj.AssertExceptionAsync<MyType, Exception>(async o => await o.ThrowException());

Known Limitations

Each test is run in a new instance of its containing class. This prevents accidental shared state between tests and allows tests to override the assertion set used. It also prevents intentional shared state between tests, but that can be overcome by defining and injecting a singleton of state.

Test names must be unique; otherwise, the adapter won’t be able to find a match, as it only looks methods up by name. You can’t have Foo(IFoo foo, Bar bar) and Foo(Bar bar, IFoo foo), either as parent tests or nested theory tests. The test discoverer will find them, but the runner will crash running them; this could be mitigated with a VS analyzer, but I haven’t written one yet.

Test method return types must be awaitable. void, Task, ValueTask, and other awaitable types are good. int and other non-awaitable types will crash.

Top-level methods (available in C# 9) cannot be theories. The reason behind this that nested methods are generated as top-level methods with a non-speakable name (something like g__Name|0__0). All top-level methods are given non-speakable names and treated as if they were a child of a Main method, so there is no way for me to identify which parent method they belong to.

Testing framework

namespace SmartUnit
{
    (AttributeUsage(AttributeTargets.Method))
    public class AssertionAttribute : Attribute
    {
        public string? Name { get; set; }
    }

    (AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false))
    public class AssertionSetAttribute : Attribute
    {
        public Type AssertionSetType { get; }

        public AssertionSetAttribute(Type assertionSetType)
        {
            if (assertionSetType.BaseType != typeof(AssertionSet))
            {
                throw new ArgumentException(null, nameof(assertionSetType));
            }

            AssertionSetType = assertionSetType;
        }
    }

    (AttributeUsage(AttributeTargets.Parameter))
    public class CallbackAttribute : Attribute { }

    (AttributeUsage(AttributeTargets.Method))
    public class SkipAttribute : Attribute
    {
        public string? Reason { get; set; }
    }

    public abstract class AssertionSet : ServiceCollection
    {
        public abstract void Configure();
    }

    internal class AssertionException : Exception
    {
        internal AssertionException(string? message) : base(message) { }
    }

    public static class AssertExtensions
    {
        public static T AssertThat<T>(this T obj, Func<T, bool> assertion, string? failureMessage = null)
        {
            if (assertion(obj))
            {
                return obj;
            }

            throw new AssertionException(failureMessage);
        }

        public static async Task<T> AssertThatAsync<T>(this T obj, Func<T, Task<bool>> assertion, string? failureMessage = null)
        {
            if (await assertion(obj))
            {
                return obj;
            }

            throw new AssertionException(failureMessage);
        }

        public static T AssertException<T, TException>(this T obj, Action<T> assertion, string? failureMessage = null) where TException : Exception
        {
            try
            {
                assertion(obj);
            }
            catch (TException)
            {
                return obj;
            }

            throw new AssertionException(failureMessage);
        }

        public static async Task<T> AssertExceptionAsync<T, TException>(this T obj, Func<T, Task> assertion, string? failureMessage = null) where TException : Exception
        {
            try
            {
                await assertion(obj);
            }
            catch (TException)
            {
                return obj;
            }

            throw new AssertionException(failureMessage);
        }
    }
}

Visual Studio test runner

namespace SmartUnit.TestAdapter
{
    (FileExtension(".dll"))
    (FileExtension(".exe"))
    (DefaultExecutorUri(ExecutorUri))
    (ExtensionUri(ExecutorUri))
    (Category("managed"))
    public class TestRunner : ITestDiscoverer, ITestExecutor
    {
        public const string ExecutorUri = "executor://SmartUnitExecutor";

        private CancellationTokenSource cancellationToken = new CancellationTokenSource();

        public void DiscoverTests(IEnumerable<string> sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink)
        {
            foreach (var testCase in DiscoverTestCases(sources))
            {
                discoverySink.SendTestCase(testCase);
            }
        }

        public void Cancel()
        {
            cancellationToken.Cancel();
        }

        public async void RunTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle)
        {
            foreach (var testCase in tests)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                await RunTestCase(testCase, frameworkHandle);
            }
        }

        public async void RunTests(IEnumerable<string> sources, IRunContext runContext, IFrameworkHandle frameworkHandle)
        {
            foreach (var testCase in DiscoverTestCases(sources))
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                await RunTestCase(testCase, frameworkHandle);
            }
        }

        private IEnumerable<TestCase> DiscoverTestCases(IEnumerable<string> sources)
        {
            foreach (var source in sources)
            {
                var sourceAssemblyPath = Path.IsPathRooted(source) ? source : Path.Combine(Directory.GetCurrentDirectory(), source);

                var assembly = Assembly.LoadFrom(sourceAssemblyPath);
                var tests = assembly.GetTypes()
                    .SelectMany(s => s.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
                    .Where(w => w.GetCustomAttribute<AssertionAttribute>() is not null)
                    .ToList();

                foreach (var test in tests)
                {
                    var testDisplayName = test.Name;
                    if (test.Name.StartsWith('<') && !test.Name.StartsWith("<<Main>$>"))
                    {
                        var parentTestName = test.Name.Split('>')(0)(1..);
                        testDisplayName = parentTestName + '.' + test.Name.Split('>')(1)(3..).Split('|')(0);
                    }
                    if (test.Name.StartsWith("<<Main>$>"))
                    {
                        var parentTestName = test.Name.Split('>')(0)(2..);
                        testDisplayName = parentTestName + '.' + test.Name.Split('>')(2)(3..).Split('|')(0);
                    }

                    var assertionAttribute = test.GetCustomAttribute<AssertionAttribute>()!;
                    var testCase = new TestCase(test.DeclaringType!.FullName + "." + test.Name, new Uri(ExecutorUri), source)
                    {
                        DisplayName = string.IsNullOrEmpty(assertionAttribute.Name) ? testDisplayName : assertionAttribute.Name,
                    };

                    yield return testCase;
                }
            }
        }

        private MethodInfo GetTestMethodFromCase(TestCase testCase)
        {
            var sourceAssemblyPath = Path.IsPathRooted(testCase.Source) ? testCase.Source : Path.Combine(Directory.GetCurrentDirectory(), testCase.Source);
            var assembly = Assembly.LoadFrom(sourceAssemblyPath);

            var fullyQualifiedName = testCase.FullyQualifiedName;
            var nameSeparatorIndex = fullyQualifiedName.LastIndexOf('.');
            var typeName = fullyQualifiedName.Substring(0, nameSeparatorIndex);

            var testClass = assembly.GetType(typeName);
            return testClass.GetMethod(fullyQualifiedName.Substring(nameSeparatorIndex + 1), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
        }

        private void RecordSkippedTest(TestCase testCase, string? reason, ITestExecutionRecorder recorder)
        {
            var now = DateTime.Now;
            var testResult = new TestResult(testCase)
            {
                Outcome = TestOutcome.Skipped,
                StartTime = now,
                EndTime = now,
                Duration = new TimeSpan(),
                DisplayName = testCase.DisplayName,
                ErrorMessage = reason
            };

            recorder.RecordResult(testResult);
        }

        private void RecordPassedTest(TestCase testCase, DateTime start, DateTime end, ITestExecutionRecorder recorder)
        {
            var testResult = new TestResult(testCase)
            {
                Outcome = TestOutcome.Passed,
                StartTime = start,
                EndTime = end,
                Duration = end - start,
                DisplayName = testCase.DisplayName
            };

            recorder.RecordResult(testResult);
        }

        private void RecordFailedTest(TestCase testCase, DateTime start, DateTime end, Exception ex, ITestExecutionRecorder recorder)
        {
            var testResult = new TestResult(testCase)
            {
                Outcome = TestOutcome.Failed,
                StartTime = start,
                EndTime = end,
                Duration = end - start,
                DisplayName = testCase.DisplayName,
                ErrorMessage = ex.Message,
                ErrorStackTrace = ex.StackTrace
            };

            recorder.RecordResult(testResult);
        }

        private async ValueTask RunTestCase(TestCase testCase, ITestExecutionRecorder recorder)
        {
            var testMethod = GetTestMethodFromCase(testCase);
            if (testMethod.GetCustomAttribute<SkipAttribute>() is not null)
            {
                RecordSkippedTest(testCase, testMethod.GetCustomAttribute<SkipAttribute>()!.Reason, recorder);
                return;
            }

            recorder.RecordStart(testCase);
            var start = DateTime.Now;

            try
            {
                if (testMethod.Name.StartsWith('<') && !testMethod.Name.StartsWith("<<Main>$>"))
                {
                    await RunNestedTest(testMethod);
                }
                else
                {
                    await RunTest(testMethod);
                }

                var end = DateTime.Now;
                RecordPassedTest(testCase, start, end, recorder);
            }
            catch (Exception ex)
            {
                var end = DateTime.Now;
                RecordFailedTest(testCase, start, end, ex.InnerException ?? ex, recorder);
            }
        }

        private async ValueTask RunNestedTest(MethodInfo test)
        {
            var parentMethodName = test.Name.Split('>')(0).Substring(1);
            var parentMethod = test.DeclaringType!.GetMethod(parentMethodName);

            await RunTest(parentMethod, test);
        }

        private async ValueTask RunTest(MethodInfo test, MethodInfo? callback = null)
        {
            var assertionSetAttribute = test.DeclaringType!.GetCustomAttribute<AssertionSetAttribute>();
            if (test.GetCustomAttribute<AssertionSetAttribute>() is not null)
            {
                assertionSetAttribute = test.GetCustomAttribute<AssertionSetAttribute>();
            }

            if (assertionSetAttribute is null)
            {
                await RunTestWithoutAssertionSet(test, callback);
            }
            else
            {
                await RunTestWithAssertionSet(test, callback, assertionSetAttribute.AssertionSetType);
            }
        }

        private async ValueTask RunTestWithoutAssertionSet(MethodInfo test, MethodInfo? callback)
        {
            var parameters = test.GetParameters().Select(s =>
            {
                if (s.GetCustomAttribute<CallbackAttribute>() is not null && callback is not null)
                {
                    return Delegate.CreateDelegate(s.ParameterType, callback);
                }

                if (s.ParameterType.IsInterface)
                {
                    var mock = (Mock)Activator.CreateInstance(typeof(Mock<>).MakeGenericType(s.ParameterType))!;
                    return mock.Object;
                }

                return null;
            }).ToArray();

            var typeInstance = test.DeclaringType!.IsAbstract && test.DeclaringType.IsSealed ? null : Activator.CreateInstance(test.DeclaringType);
            if (test.DeclaringType.IsAbstract && test.DeclaringType.IsSealed)
            {
                await InvokeTest(test, typeInstance, parameters);
            }
            else
            {
                await InvokeTest(test, typeInstance, parameters);
            }
        }

        private async ValueTask RunTestWithAssertionSet(MethodInfo test, MethodInfo? callback, Type assertionSetType)
        {
            var assertionSetInstance = Activator.CreateInstance(assertionSetType) as AssertionSet;
            assertionSetInstance!.Configure();
            assertionSetInstance.AddSingleton(test.DeclaringType!);

            var provider = assertionSetInstance.BuildServiceProvider();
            var parameters = test.GetParameters().Select(s =>
            {
                if (s.GetCustomAttribute<CallbackAttribute>() is not null && callback is not null)
                {
                    return Delegate.CreateDelegate(s.ParameterType, callback);
                }

                var service = provider.GetService(s.ParameterType);
                if (service != null)
                {
                    return service;
                }

                if (s.ParameterType.IsInterface)
                {
                    var mock = (Mock)Activator.CreateInstance(typeof(Mock<>).MakeGenericType(s.ParameterType))!;
                    return mock.Object;
                }

                return null;
            }).ToArray();

            var typeInstance = test.DeclaringType!.IsAbstract && test.DeclaringType.IsSealed ? null : provider.GetRequiredService(test.DeclaringType);
            await InvokeTest(test, typeInstance, parameters);
        }

        private async ValueTask InvokeTest(MethodInfo methodInfo, object? typeInstance, object?()? parameters)
        {
            var isAwaitable = methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
            if (isAwaitable)
            {
                await (dynamic)methodInfo.Invoke(typeInstance, parameters)!;
            }
            else
            {
                methodInfo.Invoke(typeInstance, parameters);
            }
        }
    }
}

Additional Resources

GitHub

collision detection – Need help improving my Flow Field and Unit Avoidance code so that the units can properly surround a unit instead of getting stuck

My setup is like this. Every update my movement system moves all of the units on the field. They use some Boid logic to avoid eachother, and a flow field that directs them towards each other. This is the result. https://imgur.com/a/CaUpfT7

My general setup is to calculate 2 flow fields, one for each player. From every field on the map (32×32 fields) I calculate the vector that points towards the closest enemy. Now this is a fine approach but I’d like to somehow take into consideration obstacles somehow (without another round of for loops)? Or should I handle that in my movement system avoidance? I was maybe thinking of doing A* once a unit is close enough to the enemy but all of these systems together seem rather hacky and overkill.

There are no obstacles currently, maybe a spell will generate it in the future. For now there are only units on the battleground without terrain or obstacles (besides units fighting eachother)

Here is some code. Firstly how I create the flowfields (kinda costly, will look into a way to optimize it further, don’t really need every single tile, can probably merge 2 or 3 together)

func (g *Grid) Update() {
    g.entityPositions(0) = g.entityPositions(0)(:0)
    g.entityPositions(1) = g.entityPositions(1)(:0)
    entities := g.world.GetEntityManager().GetEntities()
    posComps := g.world.GetObjectPool().Components("PositionComponent")

    for _, ent := range entities {
        g.entityPositions(ent.PlayerTag) = append(g.entityPositions(ent.PlayerTag), posComps(ent.Index).(components.PositionComponent).Position)
    }

    for x := 0; x <= g.MaxWidth/FlowTileSize; x++ {
        for y := 0; y <= g.MaxHeight/FlowTileSize; y++ {

            curPosVector := engine.Vector{X: float32(x * 32), Y: float32(y * 32)}
            // find closest tile to this one for both players
            minDist := float32(100000.0)
            minIndx := -1
            for indx, pos := range g.entityPositions(0) {
                d := engine.GetDistanceIncludingDiagonal(pos, curPosVector)
                if d < minDist {
                    minIndx = indx
                    minDist = d
                }
            }

            //  fmt.Printf("CurPos : %v, enemyPos : %v,  direction %v n", curPosVector, g.entityPositions(0)(minIndx), g.entityPositions(0)(minIndx).Subtract(curPosVector).Normalize())
            g.flowTiles(1)(x)(y).Direction = g.entityPositions(0)(minIndx).Subtract(curPosVector).Normalize()

            minDist1 := float32(100000.0)
            minIndx1 := -1
            for indx, pos := range g.entityPositions(1) {
                d := engine.GetDistanceIncludingDiagonal(pos, curPosVector)
                if d < minDist1 {
                    minIndx1 = indx
                    minDist1 = d
                }
            }

            g.flowTiles(0)(x)(y).Direction = g.entityPositions(1)(minIndx1).Subtract(curPosVector).Normalize()
        }
    }
}

And my movement code. A lot of code but just basic allignnment cohesion/ separation. With added 2 look ahead vectors to steer away from the collision.

        desiredDirection := world.Grid.GetDesiredDirectionAt(positionComp.Position, tag)
        direction := movementComp.Direction
        maxSpeed = movementComp.MovementSpeed

        //Avoidance
        nearbyEntities := helper.GetNearbyEntities(100, world, index)

        avoidance := engine.Zero()
        avoidance = avoidance.Add(alignment(world, nearbyEntities, direction).MultiplyScalar(alignmentCoef))
        avoidance = avoidance.Add(cohesion(world, nearbyEntities, direction, positionComp.Position).MultiplyScalar(cohesionCoef))
        avoidance = avoidance.Add(separation(world, nearbyEntities, direction, positionComp.Position).MultiplyScalar(separationCoef))


        //Checking ahead of us whether or not we'll encounter something
        lookAheadVectorLong := direction.Add(desiredDirection).MultiplyScalar(maxSpeed * 2.5)
        lookAheadVectorShort := direction.Add(desiredDirection).MultiplyScalar(maxSpeed)
        maxAvoidanceForce := float32(1.0)

        checkPosShort := positionComp.Position.Add(lookAheadVectorShort)
        checkPosLong := positionComp.Position.Add(lookAheadVectorLong)

        collidedIndexShort := world.Grid.IsPositionFree(index, checkPosShort, positionComp.BoundingBox)
        collidedIndexLong := world.Grid.IsPositionFree(index, checkPosLong, positionComp.BoundingBox)

        if collidedIndexShort != -1 {
            direction = engine.Zero()
            avoidance = checkPosShort.Subtract(world.ObjectPool.Components("PositionComponent")(collidedIndexShort).(components.PositionComponent).Position).Normalize()
            avoidance = avoidance.MultiplyScalar(maxAvoidanceForce * 1.5)
        } else if collidedIndexLong != -1 {
            direction = direction.MultiplyScalar(breakingForce)
            avoidance = checkPosShort.Subtract(world.ObjectPool.Components("PositionComponent")(collidedIndexLong).(components.PositionComponent).Position).Normalize()
            avoidance = avoidance.MultiplyScalar(maxAvoidanceForce * 1.2)
        }

        direction = desiredDirection
        direction = direction.Add(avoidance).Normalize()

        positionComp.Position = positionComp.Position.Add(direction.MultiplyScalar(maxSpeed))

        positionComp.Position.X = engine.Constraint(positionComp.Position.X, 0, 799)
        positionComp.Position.Y = engine.Constraint(positionComp.Position.Y, 0, 511)

        movementComp.Direction = direction.Normalize()

        world.ObjectPool.Components("PositionComponent")(index) = positionComp
        world.ObjectPool.Components("MovementComponent")(index) = movementComp

        fmt.Printf("I %d am at %vn", index, positionComp.Position)
    }

}

func limit(p engine.Vector, lim float32) engine.Vector {
    if p.X > lim {
        p.X = lim
    } else if p.X < -lim {
        p.X = -lim
    }
    if p.Y > lim {
        p.Y = lim
    } else if p.Y < -lim {
        p.Y = -lim
    }
    return p
}
func alignment(world *game.World, siblings ()int, velocity engine.Vector) engine.Vector {
    avg := engine.Vector{X: 0, Y: 0}
    total := float32(0.0)

    for _, siblingIndex := range siblings {
        avg = avg.Add(world.ObjectPool.Components("MovementComponent")(siblingIndex).(components.MovementComponent).Direction)
        total++
    }
    if total > 0 {
        avg = avg.DivideScalar(total)
        avg = avg.Normalize().MultiplyScalar(maxSpeed)
        avg = avg.Subtract(velocity)
        avg = limit(avg, maxForce)
        return avg
    }
    return engine.Vector{X: 0.0, Y: 0.0}

}

func cohesion(world *game.World, siblings ()int, velocity engine.Vector, position engine.Vector) engine.Vector {
    avg := engine.Vector{X: 0, Y: 0}
    total := float32(0)

    for _, siblingindex := range siblings {

        avg = avg.Add(world.ObjectPool.Components("PositionComponent")(siblingIndex).(components.PositionComponent).Position)
        total++

    }
    if total > 0 {
        avg = avg.MultiplyScalar(1.0 / total * cohesionCoef)
        avg = avg.Subtract(position)
        avg = avg.Normalize().MultiplyScalar(maxSpeed)
        avg = avg.Subtract(velocity)
        avg = limit(avg, maxForce)
        return avg
    }
    return engine.Vector{X: 0.0, Y: 0.0}
}

func separation(world *game.World, siblings ()int, velocity engine.Vector, position engine.Vector) engine.Vector {
    avg := engine.Vector{X: 0, Y: 0}
    total := float32(0)

    for _, siblingIndex := range siblings {
        siblingPos := world.ObjectPool.Components("PositionComponent")(siblingIndex).(components.PositionComponent).Position
        d := position.Distance(siblingPos)
        if d < desiredSeperation {
            diff := position.Subtract(siblingPos)
            diff = diff.Normalize()
            diff = diff.DivideScalar(d)
            avg = avg.Add(diff)
            total++
        }
    }
    if total > 0 {
        avg.DivideScalar(total)
    }

    if total > 0 {
        avg = avg.MultiplyScalar(1.0 / total * separationCoef)
        avg = avg.Normalize().MultiplyScalar(maxSpeed)
        avg = avg.Subtract(velocity)
        avg = limit(avg, maxForce)
    }
    return avg
}

What I am trying to achieve is:

Units not mashing into each other and just positioning themselves in a free spot around their target.

What are my problems :

  1. Make the flow field direct them away from collision rather than just towards closest unit.
  2. Make it work with the current system without adding too many nested loops and awful checks.
  3. I am doing the avoidance correctly? I have a desired direction that I get from the flow field (that directs me towards closest enemy), then I add avoidance to it to avoid any other units in the area.

My units move really well up untill the point of collision/ going to a spot next to a target. I am not sure how to implemenent that behaviour yet.

(This is my forth iteration of the movement system. I went from pure boid, to grid based, to A*, to this. So I tried a lot of variations and this going surrounding behaviour has been bugging me every time.)

Custom engine (server) written in Golang and then dispatched to Godot for the visuals. Performance is not my concern, but this is a server, so I am more mindful of that, I could probably brute force it but I’d rather hear some better take on it.

Any suggestion/article/ video is greatly appreciated !!

Edit: Thinking about it, flow field is currently useless. I can basically just have a vector pointing to the closest enemy for each unit… Would be less costly as well. But the problem of them clumping and not knowing how to surround still stands.

Should unit tests assert the inputs to dependencies?

tl;dr: Is it a unit test or an integration test, to ensure that the inputs to a mocked dependency were correct?

More details:

Suppose I’m given a requirement like this:

Create a function that returns the current temperature somewhere on Earth, given the latitude and longitude of that place. Signature has to be int getTemp(double latitude, double longitude).

Of course I have to consult an external service to get that value. One that’s available (SuperWeather) has an appropriate API I can call on, and its signature is WeatherInfo getWeather(double longitude, double latitude) (note the reversed order of lat/lon).

I set up a mock of SuperWeather for my unit tests, and run a test like this:

void ReturnsTemperature()
{
    getWeatherMock.returns(new WeatherInfo() { temperature = 17 });

    int result = SUT(1.2, 3.4);

    assert(result).equals(17);
}

The thing is that this test does not make sure I pass in the correct location to SuperWeather. I could write my function give the latitude first while it’s supposed to be second (or even make it pass hard-coded values instead of its inputs) and the test would pass.

So I’d argue that I should add another test, like

void GetsWeatherInfo()
{
    SUT(1.2, 3.4);

    assert(getWeatherMock).wasCalledWith(3.4, 1.2);
}

Discussing the above with a colleague, I was told that the latter is an integration test, and if I’ve made a mistake with the parameters to SuperWeather I should let the person doing integration tests find this out.

I argued that as long as there is a reasonable chance that the unit I wrote has a problem in it and said problem does not depend on what other units do, there should be a test that will catch that problem. The second test is still testing the unit I wrote, not anything else.

Does using a dependency make it an integration test, even if the behaviour of the dependency is not being tested?

unit testing – Testable pattern for bytecode interpreter

I am developing virtual machine for prototype-oriented language. And i found one problem.

I dont know how to write interpreter that can be tested using unit tests – naive implementation of interpret needs whole environment to do anything and making unit testing impossible.

Only idea i have is too split bytecode executor(executing bytecodes) and environment holder (holding frames etc.)
But i think this is not enough.
Is there any document about developing testable interpreters?

battery – Should I carry extra batteries in the field or bring a solar charging unit for a 30 day trek in Nepal?

If having a solar charger, you should note the limitations of law of energy conservation.

Sunshine at best produces 1000 watts / square meter. However, best single junction solar panels are around 20% efficient so you get out 200 watts / square meter. Add 95% efficient charger (you need DC-DC converter somewhere, you can’t just plug a raw solar cell into a raw battery) you get 190 watt-hours of charge per square meter per hour.

A 20 cm x 20 cm square solar panel thus provides 7.6 watt-hours of charge per hour. A Canon LP-E6NH battery is 15.762 watt-hours. To charge it at 95% battery charging efficiency (which is a different thing than DC-DC converter efficiency) you need 16.592 watt-hours of energy. Your 20 cm x 20 cm solar panel produces it in 2 hours 11 minutes. So, not even 2 hours is enough to charge the battery!

If you think a smaller solar cell let’s say 15 cm x 15 cm is enough, it needs 3 hours 53 minutes of charging.

To make matters worse, this requires the solar cell placed at an optimal angle towards the sun. Most likely, the sun is at 45 degree angle on the average and your cell at 90 degree angle (you lay it flat on the ground) so the charging time is multiplied by 1/sin(45*pi/180) or the square root of 2. So your 15 cm x 15 cm panel now charges the battery in about 5 and half hours. That’s nearly half of the average sunlight hours of 12 hours (of course some seasons have more and some seasons have less sunlight hours).

Is that fast enough for you? Most likely you won’t want to spend half of your sunlight hours staying standstill charging one camera battery!

Also note that as is well-known among photographers, clouds can reduce the exposure by 2 stops. This means energy is one fourth of what it is without clouds. So the 5 and half hours in cloudy weather is 22 hours. The trouble is, there’s no 22 sunlight hours in a single day.

So, if selecting a solar charger, please do make sure the panel is large enough that the charging time is acceptable. I would say 20 cm x 20 cm is minimum, and even then you can only reasonably use it during sunny hours unless you consider it acceptable a charge takes the whole day. Clouds? No way.

Because you need to consider that there can easily be 5 consecutive cloudy days, you most probably need not just the solar charger but also few spare batteries.

commutative algebra – Can every element of the completion $widehat{A}$ of a local ring $A$ be written as an element of $A$ with a unit of $widehat{A}$?


Your privacy


By clicking “Accept all cookies”, you agree Stack Exchange can store cookies on your device and disclose information in accordance with our Cookie Policy.




Is (0,1) and a unit circle in 2 dimensional space homeomorphic?

I think this is the 1-dimensional case of Poincare conjecture, right?

angular – How to write jest unit test for a observable variable that’s only used on template “Cannot read property ‘pipe’ of undefined”?

I have a simple angular component that has a constructor and observable variable used on the html template. I am very new to creating jest unit tests and not sure why this doesn’t work:

voice-summary.component.ts

export class VoiceSummaryComponent {
  vm$ = this._voiceService.accountDetails$.pipe(
    map(state => state.billingAccount)
  );

  constructor(private _voiceService: VoiceService) {}
}

voice-summary.spec.ts

import { Injectable } from '@angular/core';
import { VoiceSummaryComponent } from './voice-summary.component';

@Injectable()
class MockService {}

describe('VoiceSummaryComponent', () => {
  let component: any;

  beforeEach(async () => {
    component = new VoiceSummaryComponent(new MockService() as any);
    component.vm$ = jest.fn();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
    expect(component.vm$).toHaveBeenCalled();
  });
});

2 Failures:

  ● VoiceSummaryComponent › should create

    TypeError: Cannot read property 'pipe' of undefined

      10 | })
      11 | export class VoiceSummaryComponent {
    > 12 |   vm$ = this._voiceService.accountDetails$.pipe(
         |                                            ^
      13 |     map(state => state.billingAccount)
      14 |   );
      15 | 


  ● VoiceSummaryComponent › should create

    expect(received).toBeTruthy()

    Received: undefined

      14 | 
      15 |   it('should create', () => {
    > 16 |     expect(component).toBeTruthy();
         |                       ^
      17 |     expect(component.vm$).toHaveBeenCalled();
      18 |   });
      19 | });


How do I set the size of a single ‘unit’ in my game?

I’m currently trying to remake/port an old game created on Unreal Engine 2.5 to my own engine, and I have access to its physics data. It’s using a scale of 80 units per meter, or 1 unit = 1.25cm.

The walk speed value for example is 5, and physics is simulated at 60 FPS, so the walking speed I need to implement would be 3.75 meters per second.

But I don’t exactly know what the size of a game engine’s "unit" actually means. I of course want my own engine (I’m using C++, OpenGL, and GLM) to use this same scale, but I’m not sure how to go about that. I’d assume this affects not only physics/movement code, but the scale of models and maps I load in as well, so it’s important to make sure it’s the same as in the original game.

How would I implement this? What parts of a game does the size of a ‘unit’ affect and how? Just the physics and movement calculations, or the graphics side as well (matrices, clipping space, FOV, etc.)?

(I’m not sure if I’m overthinking things)