Frequently Asked Questions - bryanedds/Nu GitHub Wiki

Nu Game Engine FAQ (new!)

What are the scalability limitations of ImSim and MMCC?

In practice, there should be none. Whether you pick ImSim or MMCC as your game's main API, you should just blithely use that API to declare everything in your game by default. Both APIs can scale to over 10,000 individually-managed entities. Note that this number is the number of entities individually declared by ImSim or MMCC. For example, the mansion in Project 5, as well as all the furniture and decor inside of it, is but a single RigidModelHierarchy entity from ImSim's perspective. This is because despite the subscene being composed of many thousands of child entities, there is only one root entity for ImSim to manage. The 10,000+ number is the number of individual entities that ImSim manages the lifetime of directly; that number doesn't include the children of the entity whose lifetime is managed by ImSim. So the likelihood of a game going above 10,000 of these at a time is extremely low. And if entities of a certain type ever surpass that threshold, you can just manually manage their particular lifetimes by falling back to the Classic Nu API via manual invocations of World.createEntity and World.destroyEntity. The engine itself scales to many millions of live entities (tho of course they can't all be visible on screen at the same).

Why should I use Commands in MMCC since Messages can do everything?

If you side-effect the world in a Message method in a way that changes the property of the model you're currently handling a message for, the model value returned from the Message will overwrite the property changes from the side-effect. This can sometimes be bothersome to debug. This is why you should use commands if the signal doesn't transform the model - this issue cannot arise in a Command method.

Can I change an entity's bound MMCC model inside its own dispatcher?

You can, but it's not generally advised.

A limitation that MMCC has (that ImSim doesn't) is that if you, say, bind a player's model from a portion of the gameplay's model, changing the player model inside the player's dispatcher will cause the data to go out of sync. Generally, MMCC models are intended to be used unidirectionally with downward data flow (IE, from Screen to Entity, but not from Entity to Screen). Rather than changing the child's model inside of its own dispatcher, it's better to publish an event that the parent observes and then have the parent update the relevant part of its own model. In this way, unidirectional model data flow is preserved.

That said, there are very careful ways to establish these sort of 'bidirectional' model data flows in MMCC that work for specific cases, but these approaches are a lot more subtle and performance-sensitive. Because of that, it's generally recommended to stick with the unidirectional, downward model data flow, publishing child events for their parent to handle as necessary.

Could certain scenarios (like the above) lead to too many gameplay model changes / Content reevaluations per frame?

If you have 1,000 children all publishing events per frame for their parent to handle, the parent will have to update its own model 1,000 times per frame, which could cause its Content function to be reevaluated 1000 times. Content evaluations tend to be quite cheap, but they're not free. If too many Content reevaluations are happening per frame, you have a few alternative approaches -

  1. If these are caused by handling very many physics events, don't handle each one. Instead, handle the single Game.IntegrationEvent that happens once per frame. By doing so, you can iterate through all of the physics events that happened in one place, updating the model and reevaluating the Content function only once.

  2. For very many non-physics events, create a non-persistent property lens on your gameplay screen that stores these events as they come in (rather than handling each one immediately). Then in, say, the gameplay screen's PostProcess override, manually process them all by getting and setting the gameplay screen's model only once.

  3. If you feel the first two workarounds cause too much friction in your specific type of game, consider converting the game to ImSim instead. ImSim seems to be better suited to physics-based, action-style games.

Look at all these cool abstractive facilities I have with F#! I bet I could build a cool new game-specific abstraction on top of Nu!

STOP. Don't do this. Nu is already abstract - it doesn't need additional layers of abstraction for general use!

Our more curious users tend to want to glom on additional abstractions they think would be neat to play with before understanding how to leverage what Nu already provides. We've had people effectively try to recreate MMCC in MMCC, we've had people try to create monads instead of learning to properly utilize basic existing coding facilities. It's a pattern of the highly curious type who seeks out things like functional programming and projects like ours.

However, just because F# gives you amazing abstractive features like higher-order programming and computation expressions, it doesn't necessarily mean they will be helpful with Nu. Nu has already used F#'s many abstractive features to bring you great programming experiences out of the box. So, first learn to leverage these existing facilities; they will be well-fitted for nearly everything you want to express - as that's what they were designed for! In the rare case you need additional abstraction, you often only need a little, a sophisticated algebra here or an interpreter pattern there, like Omni Blade's Cue system -

https://github.com/bryanedds/Nu/blob/46faeb4b7a6dfd2de8effd168b2b989bf991eb7e/Projects/Omni%20Blade/Core/Data.fs#L583-L703

https://github.com/bryanedds/Nu/blob/omni-blade/Projects/Omni%20Blade/Field/Field.fs#L757-L1138

Why maintain a mutable branch when Nu's Imperative mode is already so fast?

In the current master branch, you can make Nu run faster by simply enabling Imperative mode so the engine uses mutation underneath the public API. This is done automatically when running games outside the editor. However, in order to achieve this, Nu has to have conditionals checking for an Imperative flag as well as use special data structures like T/UCollections. Together, these incur some minor overhead, even when running in Imperative mode.

The mutable branch removes all these flag checks and uses the mutable collections from .NET rather than the T/UCollections to eliminate this overhead. The trade-off is that gameplay undo / redo is impossible in the mutable branch. Regular editor undo / redo will be possible, but it's not implemented yet without non-Imperative mode, which is totally absent in mutable branch. Someday we will implement mutable editor undo / redo, but it will always be missing gameplay undo / redo (unless we implement manual serialization snapshotting, which we might).

Because of techno-cultural differences, large existing game studios might feel the need to use mutable as a way to avoid fully jumping into the functional approach. The mutable branch represent a cultural half-step into the functional world as well as an option to either stay there or go the rest of the way. We're just happy if we can make new game programming form factors like ImSim and MMCC available to more developers!

Why isn't Nu just a library / submodule?

Like Unreal, Nu distributes itself as an extensible monolith. The reason is plain - no significant game ships without engine modifications. Further, many of those modifications are necessarily game-specific with little to no utility for upstreaming. A game engine inherently isn't a library, and even rather more than just a framework. Building a game from an existing game engine is more like 'game modding' than it is creating a generic .NET project and pulling in various nuget packages. However, this does turn several things on their head, especially wrt pulling down and upstreaming engine changes.

A good approach to making your engine changes easy to upstream is to do only project-specific changes in a branch. That way, all the changes you make in your master branch can be upstreamed without publicly leaking any project details. Even better would be to create your generalized engine modifications in a feature branch off of the master branch then submit PR's from those!

Why is Nu not maximally type-safe in this respect or that?

The common and quite good advice that functional programmers carry - such as those who take an interest in Nu - is that of making illegal states unrepresentable. This is often done by making a program's types (both internal and external) as granular and strictly applied as possible. But in the practical context of game engineering, maximizing type safety is often less important than other engineering attributes such as simplicity, decoupling, and developer usability. What's more, one often has to choose from a subset of these engineering attributes as they sometimes conflict. Of all practical game engines, Nu is certainly by far the most type-safe. However, Nu's type safety has only ever been a niceity that naturally comes with our implementation language F#. Type safety is not at all the primary reason we chose F# or our general engineering approaches. Rather, we chose F# and our engineering approaches to achieve more domain-relevant engineering attributes such as simplicity, understandablity, and declarativeness. Type safety is definitely important if you can get it well at a reasonable price, but often not at the cost of higher priority engineering attributes.

More generally, type safety and memory safety are not as important in game development as they are in other domains. So tools and approaches that over-prioritize these concerns tend to be less applicable if not downright problematic. Fortunately, F# seems to strike just the right balance in our case.

How do I work with Nu from a private repository?

Some games need to be developed privately outside of the public view, such as for commercial reasons. This will require an augmented workflow of the typical github model -

Here are the recommended steps -

  • Fork Nu as usual, keeping said fork public.
  • Make a private clone of your fork. This is where you will actually work on your game.
  • On your clone, add a remote to your fork (EG, git remote add public-fork https://github.com/my-user-name/Nu.git).
  • Update Nu in your private clone as needed (EG, git pull public-fork master).
  • If you need to upstream an engine change, you can either -
    • Make the changes in your public fork, pull them into your private clone for testing, then submit a PR from your fork.
    • Do the following in your private clone -
      • Make the engine changes in a feature branch of your private clone.
      • Push said feature branch to your public fork (EG, git push -u public-fork my-feature:my-feature).
      • Submit a PR to Nu from that public branch as usual.

Do you have generated API documentation?

We do here!