Search Unity

Run time trending

Discussion in 'Scripting' started by Kiwasi, Aug 8, 2015.

  1. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,463
    Ages ago I built a little run time trending tool for a project I was doing. It involved some complex variable interactions, and it was useful to be able to see the trend the variables were making together. I submitted it to the asset store at the time. They rejected the submission, and provided me with some things to do if I wanted to get it accepted. Its been sitting on my drive ever since, occasionally being pulled out for complex debugging jobs, but mostly doing nothing.

    Anyway after pulling someone else's code down from public repo and saving myself half a day of development on another project I realized I could do the same thing with this. After all, I'm not likely to go back and finish off the project to the asset stores satisfaction.

    Here is a link to the repo. Feel free to download and do stuff with it. If you make any cool changes send me a pull request and I'll drag them back in.

    And if you are really feeling sadistic jump into the editor script. I do all sorts of crazy reflection stuff. I was quite proud of it at the time of writing. Looking back on it it may just have been overkill. But I would be genuinely curios to see what some of the other scripting section veterans think of it.
     
  2. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    791
    I'm hardly qualified to look at this since I'm still really new to the Unity framework but I thought you might appreciate any feedback (and a free bump).

    I'm not 100% sure what you would use this for. What is your use case?
    Also, nitpicky semantics, what kind of "trending" is being done here? It sounds more like "tracking" to me.

    Here's some random thoughts I had while browsing your code:
    Couldn't help but notice your save function doesn't do anything since you commented out the WriteAllLines.
    Also noticed you are using the + operator on your strings a fair bit. I doubt performance is really a concern here though..
    In case you haven't discovered it yet, Path.Combine is a more semantically rich way of building up paths.
    The reflection bit I find a bit hairy -- could be the variable names..
    Personally I would go with a recursive algorithm here because it would probably be easier to read but that's just a styling thing.
    Finally, and this is totally overkill but maybe a fun learning project, you might be interested in reading about Expression Trees. I once made a lexer/parser to translate strings written with Smalltalk code syntax into an Expression tree so that I could take advantage of LINQ2SQL. You could do something similar here to improve the flexibility of the code so it would be easier to add functionality for indexers and generic types. Expression trees are also bindable (compile into IL), so they should be quite a bit faster than reflection.
     
    Kiwasi likes this.
  3. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,463
    Thanks for having a look.

    The main use case was a particularly tricky error I had to debug where a bunch of floats were randomly turning to infinity. At the time I suspected something like a divide by zero, but I had no idea which variable was causing the problem. Writing dozens of Debug.Log statements or watching multiple values with the inspector in debug mode was painful. The trender let me watch those floats and let me figure out the patterns that occurred just before the values turned infinite. I was also looking to learn editor scripting and reflection at the time.

    It might be my chemical engineering background, but trending a float has been useful in a lot of situations. I found myself doing it quite frequently once I had the tool. Checking for stability over time is probably my most common use case now. I still occasionally hit debugging problems where I need to see the graphs to figure out what is happening.

    Trending was the term I would use in my day job as a chemical engineer. I have no idea if this translates over to proper use in computer science.

    Whoops, that I can fix.

    I'll check out Path.Combine. Not something I've come across yet. I wasn't too picky on performance for the code. I'm not sure how practical it would be in a build.

    That could be it. I think the code itself is hairy too. Their are some weird hacks in there to make it work. I'll see what I can do.

    Good point. Its an artefact of the way the code was built, I started off with just hitting floats at the top level, then one more level down, then the weird looping you see now. Recursive would do a better job of describing what I'm actually doing.

    Looking back at it I think the whole trender might be overkill for its use. So I have no problem adding more overkill as part of the learning process. I'll check out expression trees.
     
  4. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    4,200
    This is pretty neat! The only thing I could see right away that should be added is support for all of the C# number primitives - doubles, bytes, uints, longs, etc.

    Are you interested in pull requests? I've got a use case for a Vector3Trender, so I'll probably write it myself, and I figured that I might as well push it upstream when I'm done.

    Rant:
    What I really want is something that would tell me why a variable changed at any point, but that is probably not doable. Something like a tool that would register the source of any and all reasons for a transform moving would be fantastic in those instances where you just can't figure out what's happening, but that would require capturing a stack trace on the engine side.
     
    Kiwasi likes this.
  5. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,463
    That's not a bad idea. Its probably simpler then it sounds too. The code relies on animation curves for the actual graph, which use floats. But it would be simple enough to scale the larger data types.

    Yeah any pull requests would be great. At the moment you can use this to trend a Vector3 (after a fashion). Simply add three trender objects and point one to each of x, y and z respectively. This is essentially what I've done in the example scene.

    That would get really messy. I can't think of a way to do this without breaking into the properties of each float. Having to modify your actual gameplay code kind of defeats the purpose of the tool. So I guess its back to attaching a debugger for this one.
     
  6. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    4,200
    Okay, sent a pull request! It's got a simple Vector3 trender, but the big change was extracting all of the trending and drawing logic away from the actual types being trended. As I've written in the pull request, it should be trivial to create trenders for any type at this point. You could also create trenders that trend an arbitrary number of different values.

    The FloatTrender looks like this:

    Code (CSharp):
    1. public class FloatTrender : Trender {
    2.     [SerializeField] private RemoteFloatField target;
    3.     [SerializeField] private AnimationCurve trend;
    4.  
    5.     protected override void GraphCurrentValue(float time) {
    6.         trend.AddKey(time, target.Value);
    7.     }
    8.  
    9.     protected override string[] DataToStrings() {
    10.         string[] data = new string[trend.keys.Length];
    11.         for (int i = 0; i < trend.keys.Length; i++) {
    12.             data[i] = trend.keys[i].time + "," + trend.keys[i].value;
    13.         }
    14.         return data;
    15.     }
    16. }
    And the VectorTrender looks like this:

    Code (CSharp):
    1. public class VectorTrender : Trender {
    2.  
    3.     [SerializeField] private RemoteVectorField target;
    4.     [SerializeField] private AnimationCurve trendX;
    5.     [SerializeField] private AnimationCurve trendY;
    6.     [SerializeField] private AnimationCurve trendZ;
    7.  
    8.     protected override string[] DataToStrings() {
    9.         string[] data = new string[trendX.keys.Length];
    10.         for (int i = 0; i < trendX.keys.Length; i++) {
    11.             data[i] = trendX.keys[i].time + "," + trendX.keys[i].value + "," + trendY.keys[i].value + "," + trendZ.keys[i].value;
    12.         }
    13.         return data;
    14.     }
    15.  
    16.     protected override void GraphCurrentValue(float time) {
    17.         trendX.AddKey(time, target.Value.x);
    18.         trendY.AddKey(time, target.Value.y);
    19.         trendZ.AddKey(time, target.Value.z);
    20.     }
    21. }
    Nice and easy!

    There's one thing I thought about changing, but didn't - the PersistentDataPath for the project is BoredMormonGames/TrendMe, so when you save to Application.persistentDataPath + "/TrendMe/", that turns into BoredMormonGames/TrendMe/TrendMe, which is a bit redundant. I could've changed it, but meh.