Search Unity

Unity is not .NET

Discussion in 'General Discussion' started by ippdev, Aug 11, 2018.

  1. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    I think FPS shooter is perhaps one of the least complex in terms of data management or derivative effects. The complexity in your game is about input management and interaction, there is very little dependent data. I can understand how you would imagine event propagation to only be very simple here, but I'm surprised you haven't encountered more complex examples.

    A simple example would be something like WorldTimePassed which notifies listeners that in game time is passing. Imagine this event triggers AI group spawn.

    If I build a component that listens for both WorldTimePassed (and updates internal record keeping) and AIGroupSpawn - the component cannot reliably know when the group spawned from its message subscriptions. The correctness of the it's time measurement will depend on the order of the subscriptions (did it subscribe to WorldTimePassed before or after the spawner).

    You may not need this kind stuff for a game that has a simpler data model, but as an experienced developer it shouldn't be a stretch to understand situations where correct ordering is crucial to maintain data integrity/correctness.

    Maintaining exact, correct data is crucial to high quality code. If your architecture sacrifices correctness for dogma - then that is objectively bad architecture.
     
  2. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Our Event bus has mainly been used to drive our MVVM-like UI (I say like becasue its not pure MVVM) and also global states for our game modes. The bots use their internal model to communicate. I agree that desktop shooters are pretty easy domain wise. Not so much in a VR shooter.
     
  3. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    In terms of raw data model management, arena shooters are very simple dude.

    How many entities do you need to serialize in your save games? How accurate is the state restoration on loading a save game? How many different kinds of interactions are there? How many ways can one game entity interact with another? How many different interaction states do you need to store? Do you even have save games?

    Not saying my game is rocket science, I don't think there is a single system in my game that has the same level of detail that your input interaction has, but the breadth of the game's data requirements are significantly broader.

    I think I've laid out my argument fairly and with a reasonable degree of accuracy. "Code smell" is a pretty subjective thing, it's a useful tool, but it should yield to reasoned analysis.
     
  4. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    It could probably been modelled more complex, but thats whats great with a good aritecture, you take a complex problem and break it down into small simple modules. The core domain in its whole is very complex but not much complex communication going on though which could explain why we don't have the problems you have seen. I'm on a AW can do a check for our event when I get home
     
  5. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    I will ask again. How do you derive the angle for the trigger? Manually discover it? i.e. someone moved the angle of the trigger and recorded its rest position and recorded the angle when the trigger was fully pulled.
     
  6. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    I was noticing a big drop in FPS and started tracking back. One simple CloseApplication using a IPointerClick interface was hogging 50FPS. I turned it into a straight monobehaviour and use the buttons OnClick delegate and gained 35FPS back. Does someone have a technical explanation as to why? What I know is I prefer EventTriggers as I seemed to get better performance from them.
     
  7. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    Again. "which could explain why we don't have the problems you have seen". This is inaccurate.

    What I've described aren't "problems" they're features, and those features require accuracy. The level of accuracy those features require cannot be achieved using a depth first system unless you add in additional data (and create dramatic increases in complexity).

    This is not some subjective thing, this is not code smell or best practice. This is objective statement of fact. That you happened to use an inferior system to achieve a more limited set of goals is great but ultimately irrelevant.
     
  8. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    I do not understand what you are asking the ratio that drives the IK is 1 to 1 mapped to the trigger on the controller.
     
  9. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    No you have choosen to solve a problem with your bus. We might have solved the same problem in another manner.
     
  10. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    You would have to solve it in another manner because your bus cannot deliver equally reliable data across a wide range of uses.

    Look, I respect the work you've done on your game. Despite a few differences in architectural taste, I'm sure you're a competent developer. This was a poor choice of arguments to dig into, let's let it go.
     
  11. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    You have a start angle and an end angle for the trigger to go through during a pull. Don't post another lick of code. It does not answer my question. How do you originally discover the start and end angle? Surely all triggers do not go through the same arc. Some may travel 20 degrees and others 27.5 degrees for example.
     
    angrypenguin likes this.
  12. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Guys I'll get back to you tomorrow when I have sobered up, to much cocktails in my blood right now. (Or maybe later because it's a fun topic)

    IMAG0788.jpg
     
    frosted likes this.
  13. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    This is strange. EventTrigger implements IPointerClick, then calls the appropriate UnityEvent. You can see the source code here. The EventSystem that handles mouse clicks, and the ExecuteEvents class that triggers the events are both entirely in C#. So on the face of it, an EventTrigger and IPointerClick should perform almost identically.

    The normal reason to choose between them is based on if you want to intercept the event in code or in the inspector.

    When I get some time I might dive in deeper and see what I can find. If there is a performance difference, its likely a bug somewhere.
     
    frosted likes this.
  14. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Sure, but that doesn't mean they can understand it as quickly as they might otherwise be able to.

    There are places in your posted example where "var" obfuscates information that would be useful to me if I were to be working with that piece of code. You're relying on IDE features, prior familiarity with the code or me manually looking at other pieces of code for the posted method to be fully understood. Some amount of those things is going to be needed, but one of your points is how self-documenting your "world class" code is, despite the fact that you're deliberately making choices which deprive readers of more direct or faster access to information.

    For instance, you asked for a single place where explicit typing would make the code more readable. Here's one:
    Code (csharp):
    1. var finger = fingerIk.fingers[(int)config.Finger];
    You could explicitly say what type your "finger" variable is here, but you've chosen not to. So, as someone not already familiar with your code, you've added a step for me to find out what type 'finger' is by going and examining that array before I can then get information about that type so I can understand the code.

    That's only easy to read for you because you're already intimately familiar with what's going on. If your 'finger' is of some complex/verbose type then I think 'var' is a pretty reasonable compromise, but if it's something simple then I see no reason not to just put that information right there.

    As for your earlier example of "var foo = 5;", that doesn't even make sense when your reasoning is "noise". Writing 'int' instead doesn't increase the amount of data to be interpreted, it just literally takes a step out of reaching that understanding while also better describing your intent.

    I see the value in automatic typing, but your argument basically comes down to implying that anyone who has trouble reading your stuff isn't "world class" enough. I want novices to be able to read my code easily so I can work on the hard stuff rather than helping them decipher things that could be easy in the first place.
     
  15. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    I turned the game object off..got 50-60fps more..turned it on and lost that. i looked at the script and it used a Monobehaviour, IPointClickHandler. I removed that interface, made it a Monobehaviour, removed EventData so you no longer passed anything into the method which was a simple one liner Application.Quit. I then pointed the EventTrigger at that function. Turning on that game object now lost me only 10-15 fps. Enterprise guy would go back to the way it was. If I got 35 extra fps from anywhere I could give a goddamn about domains, .NET practices being violated or what have you. A game dev will keep those gains and move on to more useful chores. I was wondering if under the hood there is pooling or one event whose delegate gets changed by whatever the event trigger is pointed at.
     
  16. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    That code sees active development right now, I moved it out because I will need it there soon, right now it makes more sense to have it closer to the usage namely, finger.weight = config.Enabled ? 1 : 0;

    The int foo = 5; was an example when I do write out the type.

    I think maybe we can move on from the var discussion, var does help readability and there for maintainability, but there are far more important factors for maintainability than using var or not.
     
  17. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    You must understand our game is very advanced, have you played the orginal FEAR? We are making bots on that level. We have hundreds of actions that are evaluated each frame, each fire team of 4 has a Director AI that cordinates each fire team, all directors talk to each other several times per second etc, etc. We choose to deeply integrate our bots domain with Node canvas and not utilize our bus at all. This way we can ensure tight cache lines etc.

    We use the bus to communicate with the bot domain, for example when a firearm is discharged that event is on the bus. Its picked up by the bot domain and distributed there locally. We have zero events that are executed from a collection. (Pretty sure, havent checked the source came home late yesterday)
     
  18. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Ah gotcha, those are just simple key frames defined by my wife who is the teams artist. I have created a tool for her so she can rig all items easily.
     
  19. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    By the way @ippdev, not to criticise you but do you mind speaking in frame times instead of FPS from now on? Just because without knowing your current FPS giving us FPS deltas is meaningless excluding relative comparisons. thanks!
     
    Kiwasi and AndersMalmgren like this.
  20. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Sure..Should have done so.. Framerate was at 450 last week. I looked up this week after watching the enterprise guys hack willy nilly into the existing 350 component setup and make some ridiculous choices and it was hovering at 200. I started to investigate and turning objects off and on.. When I turned off the object in question my fps went up to 250-260. Turned it on and I was back to 200-205. Checked a few times and same thing. I opened the script, removed the IPointerClickHandler and the passed in eventData and simply pointed the buttons OnClick delegate at the function and my framerate went up to 235-240. Turned it off and on and it was 250-260 with it off and 235-240 with it on.
     
  21. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    Honestly I think ms deltas are just the easiest way to compare
     
    Kiwasi and angrypenguin like this.
  22. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    What I'm trying to say is that the core requirements of the game are not broad, although they may be deep, there is a difference.

    For example, I drive all world simulation from the message system with WorldTimeChange events, these are fired per frame and represent an additional timescale. There are hundreds of agents, each requiring some degree of individualized AI. Peasant groups travelling from town to town, caravans with a multi leg journey, militia groups defending towns, bandit groups ambushing roads, etc. Each of these groups contain individual characters and each character has individualized stats and equipment. To save on memory (particularly in save game files) each AI character is virtualized until the details needs to be fleshed out.

    Quests and other activities can relate to individual characters, character parties, towns, etc. Since all of these can be destroyed (including quest givers), all of this data needs to remain flexible and responsive. Because AI vs AI combat is frequent, save games need to save and restore agents in a wide variety of states, including serialization of ai state.

    Having something like "all" listeners isn't just academic, and often I'll toss out a component to sample certain ai behaviour just for testing or diagnostic.

    That's all on the 'world map' layer, the tactical layer has entirely different AI needs and also employs a "director" that coordinates multiple subgroups. The tactical AI is kinda a mess at the moment, but structurally it's very similar with a coordinator that controls subgroups which direct individual actors which is fed into local ai which is fed into animation for root motion driven locomotion. Each AI should be capable of running in turn based mode or in real time.

    Each AI also has a personal timescale, where individual characters will freeze or unfreeze (including any individually mapped particle fx).

    I'm not saying that my game is the most complex project in history or anything. I'd even say that none of the elements in my game are particularly complex individually - I don't have any systems I'd consider particularly deep. The complexity is in scope and breadth. There are a lot of systems, and even if they're not individually particularly demanding, the sheer number of systems that need to be managed are mammoth.

    The needs of a project will vary from one instance to the next, some games have complexity coming from few high detail systems, other games will have complexity from many lower detail systems, some AAA games have many high detail systems. Things vary.
     
    AndersMalmgren likes this.
  23. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    The problem with that approach is even if your data is structs your still need to invoke your Handlers etc which are on the heap. We can crunch extremly large struct collections in our bot engine with almost zero overhead.

    We use our bus on a much higher level than you do. We use it for stuff like telling the firearm that the fire mode selector was flipped, but mostly for UI MVVM stuff. Example here. From our control mappings UI, a single Command mapping row communcates with the controller and its siblings.

    Code (CSharp):
    1. public class CommandMappings : MonoBehaviour, IEventHandler<CommandButtonMappedEvent>
    2.     {
    3.         private static readonly Command[] mappableCommands = new[] { Command.BoltRelease, Command.ChangeFireMode, Command.MagRelease, Command.ReleaseAction, Command.ReleaseDrum, Command.CockHammer };
    4.  
    5.         private static readonly Command[][] commandGroups = new []
    6.         {
    7.             new[] { Command.BoltRelease, Command.ChangeFireMode, Command.MagRelease },
    8.             new[] { Command.ReleaseDrum, Command.CockHammer }
    9.         };
    10.      
    11.         private Dictionary<Command, CommandMapping> maps;
    12.  
    13.         protected virtual void Start()
    14.         {
    15.             EventBus.Instance.Subscribe(this);
    16.  
    17.             var template = GetComponentInChildren<CommandMapping>();
    18.             template.gameObject.SetActive(false);
    19.  
    20.             maps = mappableCommands
    21.                 .Select(cmd => Instantiate(template).Init(cmd)
    22.                 .ToDictionary(cm => cm.Command, cm => cm);
    23.  
    24.             foreach (var item in maps.Values)
    25.                 item.transform.SetParent(transform, false);
    26.         }
    27.  
    28.         protected virtual void OnDestroy()
    29.         {
    30.             EventBus.Instance.Unsubscribe(this);
    31.         }
    32.  
    33.         public void Handle(CommandButtonMappedEvent evt)
    34.         {
    35.             var validStates = new Dictionary<Command, bool>();
    36.             validStates[evt.CommandMapping.Command] = true;
    37.  
    38.             foreach (var affectedGroup in commandGroups.Where(cg => cg.Any(c => c == evt.CommandMapping.Command)))
    39.             {
    40.                 foreach (var groupedCommand in affectedGroup)
    41.                 {
    42.                     var valid = !affectedGroup.Any(ag => ag != groupedCommand && maps[ag].ButtonCandidate == maps[groupedCommand].ButtonCandidate);
    43.                     validStates[groupedCommand] = validStates.ContainsKey(groupedCommand) ? validStates[groupedCommand] && valid : valid;
    44.                 }
    45.             }
    46.  
    47.             foreach (var validState in validStates)
    48.             {
    49.                 maps[validState.Key].Invalidate(validState.Value);
    50.             }
    51.         }
    52.     }
    Code (CSharp):
    1. public class CommandMapping : MonoBehaviour, IEventHandler<WaitingEvent>
    2.     {
    3.         public UIButton MapButton;
    4.         public Text Label;
    5.         private Text buttonText;
    6.  
    7.         public Button ButtonCandidate { get; private set; }
    8.         public Command Command { get; private set; }
    9.  
    10.         private bool isWaiting;
    11.         private IInputStrategy inputStrategy;
    12.  
    13.         private static readonly Dictionary<Command, string> commandLabelOverrides = new Dictionary<Command, string> { { Command.ReleaseDrum, "Release cylinder" } };
    14.  
    15.         private static readonly Dictionary<Command, Command[]> interchangeableCommands = new Dictionary<Command, Command[]> { { Command.CockHammer, new[] { Command.UncockHammer } } };
    16.         private List<Command> affectedCommands;
    17.  
    18.         protected virtual void Awake()
    19.         {
    20.             buttonText = MapButton.GetComponentInChildren<Text>();
    21.             MapButton.onClick.AddListener(WaitPlayerMappableButtonPress);
    22.         }
    23.  
    24.         protected virtual void Start()
    25.         {
    26.             EventBus.Instance.Subscribe(this);
    27.         }
    28.  
    29.         protected virtual void OnDestroy()
    30.         {
    31.             EventBus.Instance.Unsubscribe(this);
    32.         }
    33.  
    34.  
    35.         protected virtual void OnDisable()
    36.         {
    37.             StopWait();
    38.         }
    39.  
    40.         public CommandMapping Init(Command cmd)
    41.         {
    42.             gameObject.SetActive(true);
    43.             inputStrategy = NVRPlayer.InputStrategy;
    44.  
    45.             Command = cmd;
    46.             affectedCommands = new List<Command> { cmd };
    47.             if(interchangeableCommands.ContainsKey(cmd))
    48.                 affectedCommands.AddRange(interchangeableCommands[cmd]);
    49.  
    50.             ButtonCandidate = inputStrategy.GetButton(cmd);
    51.             UpdateButtonText();
    52.  
    53.             SetLabel();
    54.  
    55.             return this;
    56.         }
    57.      
    58.         public void WaitPlayerMappableButtonPress()
    59.         {
    60.             if (isWaiting)
    61.             {
    62.                 StopWait();
    63.             }
    64.             else
    65.                 StartCoroutine(DoWaitPlayerMappableButtonPress());
    66.         }
    67.  
    68.         private void StopWait()
    69.         {
    70.             if (!isWaiting) return;
    71.  
    72.             StopAllCoroutines();
    73.             MapButton.interactable = true;
    74.             isWaiting = false;
    75.             NVRPlayer.Instance.EnableMove();
    76.  
    77.             UpdateButtonText();
    78.             PublishWaitEvent(false);
    79.         }
    80.  
    81.         private void UpdateButtonText()
    82.         {
    83.             buttonText.text = inputStrategy.GetButtonCaption(ButtonCandidate);
    84.         }
    85.  
    86.         private void PublishWaitEvent(bool isWaiting)
    87.         {
    88.             EventBus.Instance.Publish(new WaitingEvent { CommandMapping = this, IsWaiting = isWaiting });
    89.         }
    90.  
    91.         private IEnumerator DoWaitPlayerMappableButtonPress()
    92.         {
    93.             isWaiting = true;
    94.  
    95.             buttonText.text = "...";
    96.             NVRPlayer.Instance.DisableMove();
    97.  
    98.             PublishWaitEvent(true);
    99.  
    100.             var hands = NVRPlayer.Instance.Hands;
    101.  
    102.             Button? input;
    103.  
    104.             while ((input = inputStrategy.CheckForPlayerMappableButtonPress(hands)) == null)
    105.                 yield return null;
    106.          
    107.             isWaiting = false;
    108.             PublishWaitEvent(false);
    109.  
    110.             ButtonCandidate = input.Value;
    111.             UpdateButtonText();
    112.  
    113.             EventBus.Instance.Publish(new CommandButtonMappedEvent(this));
    114.  
    115.             while (hands.Any(h => h.Inputs != null && h.Inputs[EVRButtonId.k_EButton_SteamVR_Touchpad].IsTouched))
    116.                 yield return null;
    117.  
    118.             NVRPlayer.Instance.EnableMove();
    119.         }
    120.  
    121.         public void Handle(WaitingEvent evt)
    122.         {
    123.             MapButton.interactable = !evt.IsWaiting || evt.CommandMapping == this;
    124.         }
    125.  
    126.         public void Invalidate(bool valid)
    127.         {
    128.             MapButton.image.color = valid ? MapButton.colors.normalColor : UI.InvalidColor;
    129.             if (valid)
    130.                 affectedCommands.ForEach(cmd => inputStrategy.UpdatePlayerCommandMapping(cmd, ButtonCandidate));
    131.         }
    132.      
    133.         private void SetLabel()
    134.         {
    135.             Label.text = GetLabel();
    136.         }
    137.  
    138.         private string GetLabel()
    139.         {
    140.             if (commandLabelOverrides.ContainsKey(Command)) return commandLabelOverrides[Command];
    141.  
    142.             return Command.Stringify();
    143.         }
    144.     }
     
  24. Deeeds

    Deeeds

    Joined:
    Mar 15, 2018
    Posts:
    739
    Going back to the some of the earlier questions about influencing the stakeholders, and doing so in front of .NET "experts" and C# "experts" weighing in with their opinions of how things should be done...

    1. unity uses C# as a scripting language, not as C#, not as .NET
    2. as a scripting language, C# has some benefits, none of which come from .NET
    3. unity is a constantly evolving game engine, getting better all the time
    4. unity is a game engine, not a corporate platform, like .NET
    5. using the latest releases of Unity has LOTS of advantages, few negatives
    6. moving up through Unity versions doesn't entail a lot of scripting changes
    7. moving up through Unity versions often bring benefits worth the effort
    8. Unity is better than it ever was, for most platforms, in 2018.1 onwards
    9. capable .NET programmers are perfectly equipped to move up versions

    Push them to upgrade, and let the .NET programmers suffer a little pain, through which they might finally realise that Unity is not a C# game engine, it's a game engine that just happens to use C# (an older version) for scripting.

    At some point they'll get it, one can hope, and realise that they have to discard their learnt .NET practices and beliefs and understand when and where they can gain advantages in Unity by (ab)using their C# knowledge, and when they need to bow to the desires/ways of the Unity game engine.

    The best site to (hopefully) humble them, is this: JacksonDunstan.com

    The amount of information on how Unity uses C# and is C++, on Jackson's site, is simply staggering.

    Further, Jackson is approachable, and might be able to write something specific that helps you convince the stakeholders they need defer to .NET programmers with a little irreverence when it comes to how Unity works.
     
    ippdev likes this.
  25. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    The event calls themselves are almost meaningless in any form. "you still need to invoke your Handlers etc which are on the heap" is poorly phrased and near jibberish. There are datasets of over a million elements that sometimes need to be evaluated to some degree (1024x1024 area).

    Like, if you're thinking of your data as a "collection" often that's an indicator that you are on a different level of granularity. Normally, large datasets are squared or cubed areas and will often range in the tens of thousands, hundreds of thousands or millions of distinct datapoints. As the number grows, different data structures are needed to efficiently handle access/query (quadtree, spatial hashes, etc).

    I'm only dealing with a couple hundred agents in world at the moment, so the overhead from notification is not worth looking into. If you think otherwise, then you need to brush up on more computationally demanding systems.

    Right now, each agent executes its update in roughly 15 microseconds, meaning 100 agents eats about a millisecond and a half of cpu. Each agent has a unique world view and that time includes re-query surroundings and includes area evaluation. Currently I don't do per frame amortization or multithreading for this, since it's not needed... so I just let every agent update every frame. Amortization would be the first step in performance optimization.

    You may have a lot of experience with architecture and the like, but my guess is that you don't have much background in high performance systems. That's fine, but don't pretend like you do :p

    (For the record, I wouldn't consider myself an expert in high performance systems (kinda mid level), but I do at least understand the rules of engagement)
     
    ippdev and QFSW like this.
  26. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    We work alot with parallelization at my dayjob, so naturally I know alot about cache lines and cache coherence. Almost every day I have to defend C# from people that want to move to a Cpp backend becasue of bad cache coherence and other nonense. There are strong guarantees in C# when it comes to the memory model. Use structs and arrays for critical data. Sure, the optimizer can make more time consuming analysis during compilation time on Cpp and other native code, but it also has less information about the environment. Theoretical optimization that you can do on native is still less useful than cache coherency, data locality and algorithm effectiveness, which can be achieved in C#. That said you dont need to think about this for every little feature. But our bots weight almost hundred of actions actions against each other each frame to make sure the bots take the best possible actions, and in worste case yuo can have more than 100 active bots at a time thats o(10000) just there. Ontop of that you have alot of raycasts, distance checks etc. All this needs to fit into the 11ms mark (90 FPS in VR) at all times, frame drops are not an option VR.
     
  27. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    C# is not a interpreted language, its compiled to MSIL and run in a CLR (Mono). Sure, there is no rule that says that scripting languages need to be interpreted, but still, I wouldnt call it scripting. Just a side note.
     
  28. QFSW

    QFSW

    Joined:
    Mar 24, 2015
    Posts:
    2,906
    But he never called it interpreted, he said its used for scripting which is what it primarily is used for in Unity
     
    angrypenguin and Deeeds like this.
  29. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Then you can call anythong that uses a API scripiting. Today I did some .NET Core scripting using the Azure API :)
     
  30. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,157
    It's not about how the language functions. It's about the way you intend to use it. A language isn't simply locked into one purpose either. It can fulfill multiple roles. C# is an excellent example of this. A programming language becomes a scripting language when you use it to extend the functionality of a core program.

    https://cs.stackexchange.com/questi...ripting-language-and-a-normal-programming-lan

    It's not just a weird idea that third party developers came up with either. Microsoft themselves make the distinction and even have tutorials on how to use C# as a scripting language in addition to using it as a programming language.

    https://blogs.msdn.microsoft.com/csharpfaq/2011/12/02/introduction-to-the-roslyn-scripting-api/
    https://blogs.msdn.microsoft.com/cd...scripting-to-your-development-arsenal-part-1/
     
    Kiwasi likes this.
  31. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Yeah but if you make a .NET core web app it's built to a class library and ran by the hosting service, so I would say most .net programs fall into that category.
     
  32. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    When yer a hammer everything looks like a nail. Please keep to the topic of the thread.
     
    angrypenguin likes this.
  33. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Dodgy. Expected.
     
  34. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    We are talking specifically about Unity. Using the 'look over there' gambit is not an option to be taken credibly within that context.
     
  35. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    That's all nice and well, but then you say things like
    which indicate some major gaps in understanding.That's literally pseudo technical jibberish. Maybe you can get away with that kind of thing at the day job, but come on man...
     
    ippdev likes this.
  36. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358

    Theres no magic, if you call the Handle method on 2000 reference types vs iterating over 2000 structs the first case will underperform
     
  37. Zuntatos

    Zuntatos

    Joined:
    Nov 18, 2012
    Posts:
    612
    I really appreciate how a part of the discussion went into overheated ad hominem attacks.
     
    teutonicus likes this.
  38. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    Dude. You loop over a list of 1k objects and call a method and the time cost is like trivial.

    Code (csharp):
    1.  
    2.   public class SomeOtherThing{
    3.     public int SomeVariable;
    4.     public void MethodCall( System.Object arg ){ SomeVariable++; }
    5.   }
    6.     public class QuickExampleAndersSilly : MonoBehaviour{
    7.     public List<SomeOtherThing> ThingList = new List< SomeOtherThing >();
    8.  
    9.     private void Awake(){
    10.       for( int i=0 ; i < 1000 ; i++ )
    11.         ThingList.Add( new SomeOtherThing() );
    12.     }
    13.     private void Update(){
    14.       Profiler.BeginSample( "Notification" );
    15.       FireNotification();
    16.       Profiler.EndSample();
    17.     }
    18.  
    19.     public void FireNotification(){
    20.       var arg = "some random reference arg";
    21.       for( int i=0 ; i < ThingList.Count ; i++ ){
    22.         ThingList[i].MethodCall( arg );
    23.       }
    24.     }
    25.     }
    26.  
    2018-08-14_14-09-59.png

    That's looking at around .03 milliseconds for 1k calls. For 200 agent notification we're looking at around 3-4 microseconds...

    "if you call the Handle method on 2000 reference types vs iterating over 2000 structs the first case will underperform"
    You're talking about 80 microseconds here in overhead from the message notification.

    Again, for 200 agents - we're talking about 3-4 microseconds... understanding the scales and overheads of individual bits and pieces and how they fit relatively is kinda important.
     
  39. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Good names, very mature. Here is my example

    upload_2018-8-14_20-44-20.png
     
    frosted likes this.
  40. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    I will admit, your names are better :D
     
    AndersMalmgren likes this.
  41. Zuntatos

    Zuntatos

    Joined:
    Nov 18, 2012
    Posts:
    612
    I'd like to point out that both these micro benchmarks are probably not representative of the thing you're trying to benchmark.

    Anders is allocating the heap-pointery-things all at once, and then instantly using them. You also may have done that benchmark on debug mode?
    Frosted is also allocating them all at once.
    Both with what looks like nothing else happening in the program whatsoever.

    Allocating them all at once with no other allocation going on probably increases the odds they're next to eachother which will mean accessing one can cache multiple others. Though the managed heap with it's garbage collector makes things a bit uncertain about the actual location of things.

    Checking access directly after allocating the pointery-things means they're still probably cached.


    But yeah the basic point is very valid. More caching & prefetching; less pointer chasing & function calls with a method(struct[]) vs a class[].method(). It basically can't be slower.

    Got to be careful when to go for the struct[] method though, since it's a bit less flexible. Got to take care your changes apply to the array-struct and not a copy, and got to manually take care of lifetime (some manual pointer system if you need to store references to your struct anywhere else).

    Sidenote, you can't have this discussion without mentioning the new ECS / job system / burst compiler that unity is working on. Especially that burst compiler is very interesting (especially if you can use it on limited methods in code not involved with the job system reasonable :) ).
     
    AndersMalmgren likes this.
  42. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    So I didn't read over the whole thread, so my apologies if these points were already brought up, but what the hell...

    At its core, Unity uses Mono. Mono is an implementation of the .NET framework. That by definition means it's not the same framework .NET developers may be used to. Even though it's supposed to behave the same as the framework they're used to, not all things are implemented the same way (if even at all).

    Unity took this a step further and changed a few rules about what to expect from C# via operator overloading -- using bool to check vs null, and overloading equals to check against a fake null being just two examples. This also broke newer C# features such as null propagating and coalescing operators. With constructors being forbidden, readonly fields become harder to set. And so on.

    Hell, Unity doesn't even use polymorphism to call Update() in MonoBehaviour derived classes. It feels a bit alien to not include "override" when creating an Update() function. But that's just how Unity works.

    So if anyone argues "But it works this way in .NET..." it's already irrelevant. Maybe when talking to the higher ups, keep making a distinction between "C# vs Unity C#" to further drive this point home.

    Some random thoughts:

    1) I would argue that if changing anything in the UI breaks the .NET code so badly that they need to be re-engineered, then the .NET code was horribly designed to begin with. If they want to stick with their .NET code, then fine. Just keep it in DLLs. And create an API to let their .NET code talk to Unity and vice versa. If they want to work within the Unity namespace, they must adhere to Unity's rules. No ifs, ands, or buts about that.

    2) Have they ever used APIs before? You use an API in the way it's meant to be used. You don't try to bend the rules or work around it, then complain that the API doesn't work the way it should.

    3) If upgrading Unity versions break your code and force you to re-engineer it, then see point #1 about adhering to Unity's rules. Note, I am not talking about Unity bugs -- these are very valid reasons on skipping certain versions while upgrading. I am talking about how if you try to access internals via reflection, then your reflected code breaks because the internals changed, well guess what, that's on you for depending on internals to never change. (Note: This should be extremely unlikely now that Unity open sourced their C# code, but the point still stands).

    4) Similar to #1 above, if they've ever worked with WPF, they should be familiar with MVVM. Or MVC. Keep the logic and UI code separate. Apply this line of thinking to Unity -- if you're going to get all fancy with your .NET code, keep it out of Unity code. Changing your code should not break Unity. Changing Unity UI should not break your code.

    5) As has already been said, game dev is very different from business dev. Unfortunately it's very hard to convey this point to higher ups, especially if they have no technical knowledge. All I can suggest is to try to use analogies or metaphors -- you have people with specialized skills and you don't try to put them on jobs they're not suited for. Even though architects and electricians work together to build a house, you don't expect an electrician to draw up blueprints for the house and expect the house to not collapse. Or architects to not die from electrocution. And so on.

    The irony in saying that games development "breaks all the rules" is that it can be tempting for a non-technical person to say, "Well isn't the opposite also true? Can't you break game dev rules then?"

    This isn't even a C# issue. I've seen similar arguments way back in the C++ days when avoiding the STL was a way to optimize your game. Avoiding the STL was a positively alien concept to business devs. Some things never change, I guess :p
     
    ippdev, Deeeds and Ryiah like this.
  43. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    To be fair, Ander's benchmark actually didn't work. The compiler removed the line where he was sqrt'ing his data, so the 36 tick benchmark was just looping through the list itself. That's why it was 20x faster. When the compiler doesn't remove the statement - avoiding the method call is like a 30% difference not 20x.

    I didn't wanna mention this because I really thought his names were hysterical and I laughed. Plus, I actually do respect @AndersMalmgren's work on his game regardless of dumb benchmarks or whatever.
     
    AndersMalmgren likes this.
  44. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    nah, this is with the sqrt removed

    upload_2018-8-15_0-35-43.png

    The compiler cant optimize it because it does not know if the code inside Math.Sqrt needs to be run. It can have internal mutable state. Since it static it shouldnt. But the compiler will not remove it.

    Edit: the compiler might optimize one thing in both cases though, it will not put the return value on the stack
     
    Last edited: Aug 14, 2018
  45. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Show him the source code for EventTrigger. Its literally doing the same thing he is.

    What I really want to know is why there is a difference between the two approaches. There shouldn't be. I strongly suspect something else is going on here.

    The discussion started off with ".NET programmers are dumb". Its only natural progression.

    I used to keep up to date. These days it makes more sense to jump to the LTS version and stay there. Especially for projects with teams and stuff.
     
  46. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    I am not sure what is occurring. My attitude is if I do something in Unity and it gives me a framerate boost I don't care about the underlying why if it is mysterious.. I will keep it as a gift.

    I decided my blood pressure was not worth the fight. I informed the stakeholders exactly how I felt, told them i was available for projects where i was in the management position. They thanked me, called me absolutely brilliant and gave me an extra 5000 bucks on the way out the door. Learned a bunch of stuff about networking and wrote tons of tools and acquired every plugin I could dream of. I am uploading 4 games to Collab that I have near completion and catching up with my peers with the latest technology Unity is bringing to bear. I will still be kicking these enterprise guys ass whilst i am gone. Frikkin' wannabes. Life is grand. Time to relax. It has been a year and four months of crunch time. The next task I see is using ECS for the forest for the Tail Of The Dragon road course game that is a replica of a famous motorcycle run just north of here. Featured in magazines like Road & Track it is 21 km with 318 curves. The terrain is 11km x 11km and modeled to within one meter of accuracy. It is surrounded by a dense subtropical rainforest and I actually went out and photographed various ground and tree bark, leaves, cliff faces, rocks and boulders to use as textures in the game. I am going to see how ECS will route a forest of a million trees through it.
     
    frosted, Kiwasi and Deeeds like this.
  47. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Why not instead try to learn why it behaves as it does?
     
  48. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    That's what I'd want to do, but in reality sometimes you've just got to move on and get the job wrapped.
     
  49. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    The profiler often indicates the faulty code so usually its not that hard finding it.
     
  50. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    I always know my code and what it does. In the four huge games I just opened I built I have no errors or warnings after upgrading from 5.1.x to 2018.2. Last time I compiled the code in question at my former workplace there were 443 warnings in the console from their framework [but i am the one who didn't know WTF was going on because C# .NET guys said so..lol]. It may be a artifact from 2017.3.f2. It may be using an IPointerClickHandler for a one off method on a one script causes eventData polling every frame. It may be there is some pooling or assigning or whatever happening when the OnClick EventTrigger delegate right on the button component is used versus a coded eventData method to determine firing.