C++ How can I create objects without having a delay?

Started by
6 comments, last by scott8 5 months, 3 weeks ago

I have this code which is called once when the state is entered for the first time the idea was each state would load/setup everything it needs to function the problem I encountered so far with this approach creating some objects such as the model takes a few seconds so when switching to the state there's a bit of a delay and I'm trying to figure out what I can do about that. I've done some research and it seems like this is where the use of threads come in so that you can load assets in the background without delaying code, but this seems just a bit out of my learning scope lol so my idea was to just do everything when the game starts, but I realized I wouldn't be able to do anything since I can't pass it to the state since it inherits from a base class so every other state would need the same parameters.

// GameState.h
class GameState {
public:
    bool initialized = false;

    virtual void Init() {
        initialized = true;
    }
    virtual void Enter(GameState* previousState) {}
    virtual void Leave() {}
    virtual void Resume() {}
    virtual void Update() {}
};

// PlayState.cpp
void PlayState::Init() {
    std::cout << "Play Initialized.\n";
    GameState::Init();
    camera = new Camera;
    shader = new Shader("Resources/Shaders/Model.vert", "Resources/Shaders/Model.frag");
    model = new Model("Resources/Models/Maria/Maria J J Ong.dae");
    idleAnimation = new Animation("Resources/Animations/Idle.dae", model);
    animator = new Animator(idleAnimation);
}

Advertisement

Using threads can be a valid option, especially when doing the loads beforehand in the background. However, when you want to use threads for deferred loading of runtime-assets, you can run into issues, like where you need to make sure all colliders are loaded so that nothing can bug through the world while the models are still loading.

The best advice I can give you is trying to use an optimized binary format for large assets, instead of text-based formats that are optimized for exchange of data. .dea seems to be one of the former formats, using XML to parse data at runtime for models is a large overhead. There exists predefined binary formats of all variants, or if your model-structure is very static you can even attempt some very primitive format yourself that just allows to dump data directly into GPU-memory without any additional processing. That way, you already cut down on a very significant part of the loading process. You can still use threads, and maybe even need to depending on the scale of the game, but it's the combination of both the ensures optimal player experience.

creating some objects such as the model takes a few seconds

You have an intrinsic performance problem: load your data faster before worrying about clever caching, preloading and multithreading strategies.

What are you burning seconds on? Can you collect profiling data? For example, you might be building something you don't actually use in shader compilation, or you might be using overly complex models that should be exported for in-game use to cheap formats with reduced data (traditionally, large polygons with textures, bump maps etc. instead of small polygons).

Omae Wa Mou Shindeiru

My recommendation: Load all resources (models, sounds, shaders) when the program is starting. Create a ‘splash screen’ with a progress bar. In the case of models I use a model class that loads and stores all models in a vector. Have a ‘Getter’ routine to retrieve the models you want , when you want them:

Model* pMaria = pModels→Get("Maria");

You're just retrieving a pointer to the model, there's no loading or delay. When the game ends, the model class deletes all models in its storage vector.

I do the same thing with sounds. I have a Sounds class that loads all sounds and a routine for playing them. Something like:

pSounds→PlaySound(eSounds::Explosion, Position, Volume);

This is known as the resource pool pattern.

Also, for going inbetween states (play, pause, options, etc) don't create new cameras. Just create one game camera and reposition it.

I hope this helps.

    shader = new Shader("Resources/Shaders/Model.vert", "Resources/Shaders/Model.frag");
    model = new Model("Resources/Models/Maria/Maria J J Ong.dae");
    idleAnimation = new Animation("Resources/Animations/Idle.dae", model);

These are a performance concern.

In games either you need to be in a non-interactive section of the program for loading, or you need to use asynchronous loading. You cannot block in interactive portions of the game.

A typical approach is loading a ton of metadata rather than the final resources. The metadata lists what the files are, it lists shapes and sizes, but doesn't have the actual resource data. That metadata is generally what gets loaded during the long load stages. The actual models, textures, and other data tends to be streamed in during gameplay.

During gameplay asynchronous IO takes place in threads, either ones that you control or that the OS does behind your back through async calls. Even so, under the approach you've shown in those lines the game can't really continue until after the resources have been loaded and processed. You'll probably want a way to preprocess the resources during a build/cook step, or with a large directory scan done during a long load phase.

@scott8 Besides from briefly going through the chapter on gameprogrammingpatterns I don't have any experience with this pattern, but from what I've gathered it seems like it's some sort of resource/object manager? If I'm understanding your message correctly I'd make one for all resources audio, models, shaders, textures(?)

@ShapeSpheres Yes, it's a resource manager. It's responsible for loading, providing, recycling, and finally deleting resources. I have different pools for different types of objects. One for models / meshes, one for sounds. I even have more complex pools for things like explosions or plasma shots that contain particles.

For something like a plasma shot, i can get a plasma from the resource manager. I can stick it in a vector where its position is updated and checked for collisions in the game loop. If it expires or hits something, I can pass it back to the resource manager, were it gets put back into a vector, and can be reset and resused when another plasma is needed. If i try to get a plasma, and the vector is empty, then I have to use NEW to create a new plasma. It basically ends up resizing the pool when needed during game play.

-Scott

This topic is closed to new replies.

Advertisement