Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor audio (pre)loading #2006

Merged
merged 35 commits into from
Feb 22, 2021
Merged

Refactor audio (pre)loading #2006

merged 35 commits into from
Feb 22, 2021

Conversation

arthuro555
Copy link
Contributor

@arthuro555 arthuro555 commented Oct 3, 2020

The current way of loading audio has a problem. There is no real preloading, so when audio is played there is a slight delay (the time for the audio file to be actually loaded). This is because we use one new Howl instance every time we want to play a sound.

This PR has for goal to cache the Howl instance of every file and reuse them. That way it is also possible to add to audio resources an attribute to let the user preload and cache the Howl instance on the loading screen.

This PR also makes HowlerSound use Composition over Inheritance, something that is preferred for this codebase if I understood correctly (Though currently it is done in a dirty way).

There is one downside though, as the Howl instances are cached, it can make the game take some more memory as they never get properly unloaded. This could be fixed with an action to clear the cache and to clear one file/resource from the cache.

Copy link
Owner

@4ian 4ian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR also makes HowlerSound use Composition over Inheritence, something that is preffered for this codebase if I understood correctly (Though currently it is done in a dirty way).

Yes composition is very usually better, in this case there is no downside to do it and is a preferred approach (because we don't need polymorphism or deal with objects of various types).

That way it is also possible to add to audio resources an attribute to let the user preload and cache the Howl instance on the loading screen.

That would be interesting! While reading the PR I was thinking of this.
Currently I think it's too risky to ask browsers to "really load" at the beginning all the music and sound files. If you do that for a game with 20 unique musics of 5mb each, you'll probably exhaust resources, memory or something bad.

We could add an attribute to the audio resources to "Preload as a sound" or "Preload as a music" - so that we have a Howl object ready to be started whenever we need it :)
We could also add 3 advanced actions: "Preload a sound in memory", "Preload a music in memory" (these two actions are doing the same thing as the audio resources marked as "Preload as a sound" or "Preload as a music". Basically, the loading screen is running these "actions" for each resource that has the attribute) and an action "Clear the preloaded sounds/musics" (that would clear this "cache" of Howl objects).

Let me know what you think!

GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
@arthuro555 arthuro555 marked this pull request as ready for review October 19, 2020 05:13
@arthuro555 arthuro555 requested a review from 4ian October 19, 2020 05:13
@arthuro555
Copy link
Contributor Author

Ok, now it is ready for proper reviewing.

Copy link
Owner

@4ian 4ian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very interesting :) My main concern is about running multiple times a sound, will this work? I'm afraid the Howl object would be shared so only one sound at a time of the same file would work?

Core/GDCore/Project/ResourcesManager.h Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
GDJS/Runtime/howler-sound-manager/howler-sound-manager.js Outdated Show resolved Hide resolved
@arthuro555
Copy link
Contributor Author

My main concern is about running multiple times a sound, will this work? I'm afraid the Howl object would be shared so only one sound at a time of the same file would work?

Well, I assumed so siince there is the whole IDs system, and that there is this as an example in the docs:
image
If not though then I have a problem because the whole PR relies on that concept 😅

@4ian
Copy link
Owner

4ian commented Oct 19, 2020

Well, I assumed so siince there is the whole IDs system

Ah nice, I think I did not understand how it works! If that's the case that's even better then :)
I re-read the documentation and it seems that it's properly handled so no worries.

@4ian
Copy link
Owner

4ian commented Oct 19, 2020

Sounds better than the old system then 👍👍

@4ian
Copy link
Owner

4ian commented Oct 19, 2020

Very excited about these changes :) Let's triple check that all conditions/actions are working, in particular with multiple sounds and loading of preloaded sound/musics/not preloaded.
Maybe a quick test game to make for this (or find one that would already exist, but I'm not sure)?

@arthuro555
Copy link
Contributor Author

arthuro555 commented Oct 20, 2020

I began to try adding typescript typecheking, sadly it doesn't support well overloaded functions :/
I added the chaining of HowlerSound methods to be consistent with howler.
I marked setRate/getRate as deprecated as both rate and setRate/getRate were used in the code, and I think duplicated APIs are bad, so I kept the one consistent with the other APIs. This shouldn't impact most users, as long as they don't use it via JS.
I fixed all the points you mentioned.

@arthuro555 arthuro555 requested a review from 4ian October 20, 2020 20:17
@4ian
Copy link
Owner

4ian commented Oct 20, 2020

I added the chaining of HowlerSound methods to be consistent with howler.

Let me know what you think, but as explained in the comment I'm not a huge fan of these magic functions, they are impossible to statically type and probably a nightmare for JIT. Not sure why people like them, writing getXxx/setXxx is not really long nor verbose and very clear.

Anyway, let me know what you think we should do. Keeping compatibility with Howler is interesting, but on the other hand in the future if we replace it, we'll have this kind of "fluent" api that does not look like anything else in the codebase to maintain.

@arthuro555
Copy link
Contributor Author

I think overloading is indeed not a very good idea. As we have autocompletion in the IDE and API docs (though I believe their generation is currently broken due to bad typing in gd.js), it shouldn't be really a problem for the users to find the syntax for GDevelop. In the worst sceneario, they can use the public createHowlerSound interface to get access to a raw Howl if they want to.

Do you think I should also remove the chaining for setters? I think that a fluent api is better when mass-setting properties of a sound.

@4ian
Copy link
Owner

4ian commented Oct 20, 2020

In the worst sceneario, they can use the public createHowlerSound interface to get access to a raw Howl if they want to

Yep. Or we could add an accessor to the howler sound if really needed one day (let's not do it now).

Totally ok for the fluent setters 👍

@arthuro555
Copy link
Contributor Author

Ok, I think all that's left is testing. Silver gave me a project for testing, let's try it out :)

@arthuro555 arthuro555 changed the title Refactor music loading Refactor audio (pre)loading Feb 22, 2021
@4ian
Copy link
Owner

4ian commented Feb 22, 2021

Thanks for the additional cleanup, I'll make a few more tests on larger games to ensure this works well, and then we can merge this :)

@4ian
Copy link
Owner

4ian commented Feb 22, 2021

I vaguely remember something related to stopped not returning true, and so free sound/musics being never destroyed in the array containing the free sound/musics (those not attached to a channel).
Can you double check this?

@4ian
Copy link
Owner

4ian commented Feb 22, 2021

I've tested on a game and can confirm preloading works well :) Background music is noticeably faster to start when marked as preloading. That's very cool.

Nothing suspicious it seems, all sounds and musics working well, Howler._howls seems to contain the played sounds, the freeSounds array seems not to leak.

@arthuro555
Copy link
Contributor Author

Nice! Is there something left to test or change or do you think this is ready to merge?

@4ian
Copy link
Owner

4ian commented Feb 22, 2021

Almost ready but still checking everything that I can. Notably this: when I call the action to unload all audio, the sound effect I was playing stops (as expected), but then when I try to play it again it does not play (but other sound effects work).

In other words: when unloading, any sound that was already loaded and played will stop (ok) but won't be able to be played again. Other sounds not yet loaded will work. This sounds counter intuitive because if the engine can load sounds never played before, it should be able to also load sounds that have been unloaded?

@4ian
Copy link
Owner

4ian commented Feb 22, 2021

I've fixed this last bug. Would be good to do another pass to be sure there is nothing else like this :)

@arthuro555
Copy link
Contributor Author

Ah nice catch, thanks for fixing it!

@4ian
Copy link
Owner

4ian commented Feb 22, 2021

_checkForPause is unused right? I'm removing it now.

@arthuro555
Copy link
Contributor Author

arthuro555 commented Feb 22, 2021

To be honest it looked so hacky that I was too scared to touch it

@4ian
Copy link
Owner

4ian commented Feb 22, 2021

Looks good overall! Let's merge this, seems to work correctly now with the "__default" for the play method :) Could you maybe write about this workaround in your GitHub issue on the Howler repo?

@arthuro555
Copy link
Contributor Author

Could you maybe write about this workaround in your GitHub issue on the Howler repo?

I already did ;)

@4ian 4ian merged commit fa98836 into 4ian:master Feb 22, 2021
@4ian
Copy link
Owner

4ian commented Feb 22, 2021

Merged! :)

@arthuro555 arthuro555 deleted the music-refactor branch February 22, 2021 20:04
dos1 added a commit to dos1/GD that referenced this pull request Jun 8, 2022
PR 4ian#2006 effectively reverted the behavior of preloaded sounds
to state from before 4ian#431, bringing back old memory consumption
issues. For games that want to download all their assets during
the loading screen, but don't want to actually decode all sounds
right away because of memory consumption concerns, being able to
dynamically load/unload Howler objects via events is not enough,
as the application may already go out-of-memory during loading,
and not doing that will cause the game to download additional
assets during gameplay. This change adds a new property,
"preloadAsFile", which makes GDJS request the sound asset via XHR.
This puts the file into browser cache, so it can be reasonably
expected to be decoded later without issuing network requests.
dos1 added a commit to dos1/GD that referenced this pull request Jun 8, 2022
PR 4ian#2006 effectively reverted the behavior of preloaded sounds
to state from before 4ian#431, bringing back old memory consumption
issues. For games that want to download all their assets during
the loading screen, but don't want to actually decode all sounds
right away because of memory consumption concerns, being able to
dynamically load/unload Howler objects via events is not enough,
as the application may already go out-of-memory during loading,
and not doing that will cause the game to download additional
assets during gameplay. This change adds a new property,
"preloadAsFile", which makes GDJS request the sound asset via XHR.
This puts the file into browser cache, so it can be reasonably
expected to be decoded later without issuing network requests.
dos1 added a commit to dos1/GD that referenced this pull request Jun 9, 2022
PR 4ian#2006 made the preloaded sounds be decoded right away,
bringing back old memory consumption issues from before 4ian#431.
For games that want to download all their assets during
the loading screen, but don't want to actually decode all sounds
right away because of memory consumption concerns, being able to
dynamically load/unload Howler objects via events is not enough,
as the application may already go out-of-memory during loading,
and not doing that will cause the game to download additional
assets during gameplay. This change adds a new property,
"preloadInCache", which makes GDJS request the sound asset via XHR.
This puts the file into browser cache, so it can be reasonably
expected to be decoded later without issuing network requests.

While at it, add user visible descriptions for all preload options.
dos1 added a commit to dos1/GD that referenced this pull request Jun 9, 2022
PR 4ian#2006 made the preloaded sounds be decoded right away,
bringing back old memory consumption issues from before 4ian#431.
For games that want to download all their assets during
the loading screen, but don't want to actually decode all sounds
right away because of memory consumption concerns, being able to
dynamically load/unload Howler objects via events is not enough,
as the application may already go out-of-memory during loading,
and not doing that will cause the game to download additional
assets during gameplay. This change adds a new property,
"preloadInCache", which makes GDJS request the sound asset via XHR.
This puts the file into browser cache, so it can be reasonably
expected to be decoded later without issuing network requests.

While at it, add user visible descriptions for all preload options.
dos1 added a commit to dos1/GD that referenced this pull request Jun 9, 2022
PR 4ian#2006 made the preloaded sounds be decoded right away,
bringing back old memory consumption issues from before 4ian#431.
For games that want to download all their assets during
the loading screen, but don't want to actually decode all sounds
right away because of memory consumption concerns, being able to
dynamically load/unload Howler objects via events is not enough,
as the application may already go out-of-memory during loading,
and not doing that will cause the game to download additional
assets during gameplay. This change adds a new property,
"preloadInCache", which makes GDJS request the sound asset via XHR.
This puts the file into browser cache, so it can be reasonably
expected to be decoded later without issuing network requests.

While at it, add user visible descriptions for all preload options.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
👀 Needs review PR needs to be reviewed by someone. 🏁PR almost ready: final fixes The PR is almost ready and needs final fixes and/or polishing before merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

"Play Music on a Channel" action not working like it used to. [GD5] Android: no music
2 participants