Pergunta

I have two classes:

public class Child {
    public List<Vector2> localPoints;
    public List<Vector2> localEdges;
}

public class Parent {
    public List<Child> children;
    public Vector2 worldPos;
}

A parent has a position in the world worldPos. localPoints and localEdges in child contains points relative to worldPos.

To get the position in the world of a Vector2 in localPoints orlocalEdges the caller can just add worldPos to each element.

      foreach (Parent parent in parents) {
        foreach (Child child in parent.children) {
            foreach (Vector2 point in child.localPoints) {
                Debug.Log(point + parent.worldPos);
            }
            foreach (Vector2 edge in child.localEdges) {
                Debug.Log(edge + parent.worldPos);
            }
        }

The issue is that i have more lists and even shapes stored in a child. Giving the caller the responsibility to add worldPos at every usage becomes very cumbersome.

How can i abstract away that offsetting without introducing additional computational complexity?

I have thought about this and found two solutions that abstracts away the offsetting:

  1. Every time worldPos change, go trough the data in each child apply the change. localPoints andlocalEdges would now be the actual positions in the world.

  2. Store worldPos in each child aswell and use a custom getter that creates a copy of the desired list, apply the offset to each element in the list and return it.

Unfortunately both of these introduce computational complexity.

How do i abstract away the offsetting without introducing computaional complexity?

Foi útil?

Solução

Abstract responsibility from caller without introducing complexity

How can i abstract away that offsetting without introducing additional computational complexity?

Abstraction is a form of complexity. We can argue about the degree of complexity, but I expect everyone to agree that an abstraction adds a non-zero amount of complexity to a codebase.

However, I do agree that this needs to be abstracted away from a caller.

How do i abstract away the offsetting without introducing computaional complexity?

Again, it's impossible to do something that doesn't take any effort. I would rephrase your expectation so that you minimize computational complexity rather than avoid it.


  1. Every time worldPos change, go trough the data in each child apply the change. localPoints andlocalEdges would now be the actual positions in the world.

This is not a good approach. You're taking the effort to convert all points, even though you don't even know if you're ever going to need them.

Suppose the world position changes, you recalculate everything (e.g. 100 vectors). Only two values are requested before the world position changes again and you have to recalculate everything again. You effectively performed 98 unnecessary conversions.


  1. Store worldPos in each child aswell and use a custom getter that creates a copy of the desired list, apply the offset to each element in the list and return it

This is closer to what you want, but not good. Now you're storing worldPos in both the parent and the child. What if you forget to update one of them?

At best, you've got duplicate data and are responsible for synchronizing them. At worst, you mismanage the synchronization and will pull your hair out trying to debug it.

A simple fix would be to have the child store a reference to its parent, where it is able to retrieve the (non-duplicate) value.

Another solution would be to always request the values via the parent, not via the child. The distinction here is contextual. Sometimes it's better to access a child via its parents. Sometimes it's better to use the child directly and have the child talk to its parent when necessary.
I assume that you're currently in a scenario where your callers have a reference to the child, not the parent.

A second consideration is how you intend to access these lists. Are you always going to iterate over them? Are you looking for specific items?

Based on your example, I'm going to assume that you're going to iterate over them and therefore won't mind converting the entire list when the list is accessed.

You'll want something like this:

public class Child {
    private Parent parent;

    public List<Vector2> LocalPoints;
    public List<Vector2> LocalEdges;

    public Child(parent)
    {
        this.parent = parent;
    }

    public List<Vector2> WorldPoints => LocalPoints.Select(localVector => parent.worldPos + localVector).ToList();
    public List<Vector2> WorldEdges => LocalEdges.Select(localVector => parent.worldPos + localVector).ToList();
}

This does what you need it to do. Nothing that you're not actually storing the "world" lists. You are simply calculating them on the fly, which means that you don't need to track changes to parent.worldPos. Whenever you calculate it on the fly, you'll automatically be working with the current value.

If you need further performance optimization when accessing single items from the list (wanting to avoid converting the unwanted items), then you need a more complex solution.


A last consideration: caching. This is a double edged sword.

  • If the vectors are fetched many times more than the worldPos actually changes, you can squeeze out some performance gains by calculating the values only once when worldPos changes.
    • However, this comes at a great cost: having to manage the caching. This takes a considerable development effort in exchange for a decreased performance effort.
  • If the worldPos changes more often than you fetch the vectors, then the "cache all" approach will actually decrease performance instead of increase it.
    • You would then need a system that caches values only when they are requested the first time, which another level of complexity on top of your "cache all" approach.

This needs to be outweighed. Since offsetting vectors is not a cumbersome calculation (it's literally doing x1+x2 and y1+y2, I would not look at caching now.
The development complexity of implementing caching does not outweigh the calculation complexity of doing a few extra number additions.

Premature optimization is usually not a good thing. Take note of the potential performance issue but wait until an actual performance problem presents itself and then look for the bottleneck. The bottleneck might be in a completely different location.

Outras dicas

Generally you don't want to abstract away the offsetting.

You have the objects well factored as is.

There may be some usage patterns where it would be more efficient to store adjustedworldcoordinates in child instead of local coordinates (they are interchangeable). But the additions you are doing are not costly. And the choice of storing local (vs global) coordinates works better if you generalize to more levels of parentage or different global coordinate systems

Your model is inconvenient in two ways.

Instead of having separate Parent and Child classes it would be a lot easier to have just one. Parent and Child are not classes of real-world objects, they are qualifications that change with the observer's perspective. So let's replace both with Thingy.

Second, a Thingy may have children but in your world where every thingy just gets updated and needs to be drawn from time to time it is much more convenient to store a reference to the parent Thingy instead. The thingy that has no parent is your world.

Then determining absolute positions will be a breeze: Parent.Position + this.Position.

Licenciado em: CC-BY-SA com atribuição
scroll top