How could I optimize this formula and structure?

Hello, I am looking for some suggestions and ideas on how to improve a structure of mine. In my project, I have this formula that represents the value property of an entity, including modifiers. If it doesn’t display, this is the raw LaTeX formula:


.
To explain this formula, a final value called, well, value is calculated by:

  • Creating the sum of a base value and the sum of all flat bonus values that should be scaled.

  • The resulting sum is multiplied with the sum of all values that represent additive percentage modifiers.

  • The resulting value of step 2 is then multiplied with the sum of all values that represent multiplicative percentage modifiers

  • Finally, the value resulting from step 3 is then summed up with the sum of all values that represent unscaled flat bonus values, unscaled as in unaffected by percentage modifiers.

For better understanding, here are some examples for these values:

  • “This hero talent increases your base health by 100 points” - This is a scaled flat bonus.

  • “This equipment piece increases your base life by 10%” - This is a additive percentage bonus.

  • “This buff increases your total health by 25%” - This is a multiplicative percentage bonus.

  • “This buff increases your total health by 250 points” - This is an unscaled flat bonus.

Now because I wanted to have something that works as quickly as possible, I translated this into literal code, having four collections that keep track of all values of each individual part of the formula, as well as a function that calculates the final value. It could look like this:

public interface FloatProvider
{
    float Value { get; }
}

public class MyEntityValue
{
    private float baseValue;

    public List<FloatProvider> scaledValues;
    public List<FloatProvider> additiveValues;
    public List<FloatProvider> multiplicativeValues;
    public List<FloatProvider> unscaledValues;

    public float Value
    {
        return (((baseValue + scaledValues.Select(x => x.Value).Sum())
            * additiveValues.Select(x => x.Value).Sum())
            * multiplicativeValues.Select(x => x.Value).Sum())
            + unscaledValues.Select(x => x.Value).Sum();
    }
}

Now aside from some obvious improvements such as introducing a new collection type to only recalculate the value of a list of values such as scaledValues if it is dirty, has changed, how could I improve on this structure?

In particular, I am curious about the collection themselves. At runtime, some lists might not actually be needed most of the time. Perhaps, an entity never has unscaled flat bonus values [from a certain point on], for instance. Is there a way to destroy lists, but regenerate them when needed? Reading about WeakReference, would this improve the performance if you need objects at some times and don’t care about it at other times?

It might not seem very useful in this particular case, however, as you might have noticed, in the formula everything has the addendum “base”, as I am still investigating if I need more layers, basically chaining the formula multiple times. When I do this three times, for instance, I already end up with twelve lists and it seems a lot to me considering that most of them won’t be needed most of the time. Are there better data structures for my case that don’t rely on a lot of individual lists?

This is definitely the most obvious, and probably the best bang for your buck.

WeakReference is used for when you have an object you don’t mind being garbage collected while you maintain a reference to it. It’s just a natural byproduct of contending with a memory manager. The manager needs to know if it can clear something out of memory, and it does so by seeing how many references to it exist. But how can you reference something and allow it to be cleared out at the same time? WeakReference.

Honestly it’s something not used all too often, but can be useful when needed. Here in Unity it doesn’t usually come up because we interact with unmanaged objects mostly that get explicitly destroyed. We can tell if the majority of the object has been removed from memory because the managed C# object == null… effectively acting a little bit sort of kind of like a weakreference to the unmanaged unity object. And this sort of relationship is pretty much what you might use a weakreference for if only in 100% managed land.

I don’t see how this would benefit your situation. Especially since you only really have the 1 reference to these lists. This means all your lists could randomly be deleted regardless of state by the memory manager. And you probably don’t want that. It’s not like ‘WeakReference’ knows when a list isn’t necessary when the necessity of it is something defined by you… not weakreference.

Your algorithm necessitates the grouping of values.

At the end of the day your bottle neck is that you choose to traverse these groupings at moment of reading the value.

Since you have to traverse it for the algorithm… and you have to account for every value in the groupings… that traversal is inherently linear. The only speeding up I could think of traversal wise is asynchronously summing since sums can ignore order. But honestly, the overhead of setting up the async summing would lose all benefits until your lists themselves became very large.

Pre-calculating the value is your best bet at speeding this up, especially as it scales. Just calculate the sum of each list as entries are added or removed, you don’t even have traverse the list in this case… you literally just +/- to some cached sum variable. Empty lists will inherently be 0.

The rest of the algorithm can be calculated on the fly as that’s not really your bottle neck. It’s the traversal of each list.

The only thing that sucks is if any given ‘FloatProvider’ can have its value change… in which case you may want a way for floatprovider to signal that it’s changed.

And if floatprovider can change frequently… well… then there’s a problem. I’d probably just flag that the entire set is dirty when something changed and only clean it on read.

1 Like

Thank you for your response, @lordofduct !

Yeah, this hit me like lightning at some point. It would delete the list even if it still contained elements which is not something that I want.

I see, I think that I keep it simple then and do as you suggested. I revisited my design and in total, I won’t need more than six lists instead of chaining the formula three times, ending up with twelve lists, giving me:

Thankfully, this doesn’t happen in my current design and I am not sure if I really want to cover this right away. It could become useful at some point, allowing the value of FloatProvider to change, but just trying to think about solving the notification gives me a headache. I mean, actually, a simple Subject-Observer-Pattern, with FloatProvider extending from it, could solve this already, I will have to see if I want this.

Thank you very much for your response, you helped me a lot!