-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Player UI list to kotlin #11829
base: refactor
Are you sure you want to change the base?
Player UI list to kotlin #11829
Conversation
And simplify the code a little
2e23e77
to
5b92de4
Compare
15d5174
to
15eca50
Compare
Okay I noticed a funny thing where threads would compete for modifying our UI list, so I added a Mutex around it. |
2f32929
to
cbade0b
Compare
Quality Gate failedFailed conditions See analysis details on SonarQube Cloud Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE |
The code quality thingy is mostly that I marked a function as deprecated and have not yet changed all use-sites because it’s not in-scope of this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a good change, overall.
fun call(consumer: java.util.function.Consumer<PlayerUi>) { | ||
// copy the list out of the mutex before calling the consumer which might block | ||
val new = playerUis.runWithLockSync { | ||
lockData.toMutableList() | ||
} | ||
for (ui in new) { | ||
consumer.accept(ui) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fun call(consumer: java.util.function.Consumer<PlayerUi>) { | |
// copy the list out of the mutex before calling the consumer which might block | |
val new = playerUis.runWithLockSync { | |
lockData.toMutableList() | |
} | |
for (ui in new) { | |
consumer.accept(ui) | |
} | |
} | |
fun call(consumer: Consumer<PlayerUi>) { | |
playerUis.forEach(consumer) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work even when called from Java?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but Unit.INSTANCE has to be returned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the parameter type back to Consumer
; the synchronized list implementation automatically handles the synchronization needed.
for (ui in lockData) { | ||
if (playerUiType.isInstance(ui)) { | ||
when (val r = playerUiType.cast(ui)) { | ||
// try all UIs before returning null | ||
null -> continue | ||
else -> return@runWithLockSync r | ||
} | ||
} | ||
} | ||
return@runWithLockSync null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for (ui in lockData) { | |
if (playerUiType.isInstance(ui)) { | |
when (val r = playerUiType.cast(ui)) { | |
// try all UIs before returning null | |
null -> continue | |
else -> return@runWithLockSync r | |
} | |
} | |
} | |
return@runWithLockSync null | |
synchronized(playerUis) { | |
playerUis.firstNotNullOfOrNull { playerUiType.kotlin.safeCast(it) } | |
} |
import java.util.Optional | ||
|
||
class PlayerUiList(vararg initialPlayerUis: PlayerUi) { | ||
var playerUis = GuardedByMutex(mutableListOf<PlayerUi>()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Collections.synchronizedList()
could be used instead, which would remove the need for a separate mutex class.
var playerUis = GuardedByMutex(mutableListOf<PlayerUi>()) | |
val playerUis = Collections.synchronizedList(mutableListOf<PlayerUi>()) |
The rest looks good to me, too, thanks! |
fun <T> destroyAll(playerUiType: Class<T?>) { | ||
val toDestroy = mutableListOf<PlayerUi>() | ||
|
||
// short blocking removal from class to prevent interfering from other threads | ||
playerUis.runWithLockSync { | ||
val new = mutableListOf<PlayerUi>() | ||
for (ui in lockData) { | ||
if (playerUiType.isInstance(ui)) { | ||
toDestroy.add(ui) | ||
} else { | ||
new.add(ui) | ||
} | ||
} | ||
lockData = new | ||
} | ||
// then actually destroy the UIs | ||
for (ui in toDestroy) { | ||
ui.destroyPlayer() | ||
ui.destroy() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In combination with the synchronized list change:
fun <T> destroyAll(playerUiType: Class<T?>) { | |
val toDestroy = mutableListOf<PlayerUi>() | |
// short blocking removal from class to prevent interfering from other threads | |
playerUis.runWithLockSync { | |
val new = mutableListOf<PlayerUi>() | |
for (ui in lockData) { | |
if (playerUiType.isInstance(ui)) { | |
toDestroy.add(ui) | |
} else { | |
new.add(ui) | |
} | |
} | |
lockData = new | |
} | |
// then actually destroy the UIs | |
for (ui in toDestroy) { | |
ui.destroyPlayer() | |
ui.destroy() | |
} | |
fun <T : PlayerUi> destroyAll(playerUiType: Class<T>) { | |
// short blocking removal from class to prevent interfering from other threads | |
val (toDestroy, toKeep) = synchronized(playerUis) { | |
playerUis.partition { playerUiType.isInstance(it) } | |
} | |
playerUis.retainAll(toKeep) | |
// then actually destroy the UIs | |
for (ui in toDestroy) { | |
ui.destroyPlayer() | |
ui.destroy() | |
} |
@Isira-Seneviratne I don’t understand your review, you said this looks like a good change, but then you proposed to replace everything with SynchronizedList instead of using a Mutex? |
I meant that the idea of refactoring to ensure thread safety is a good one. Sorry if I caused any confusion. |
Personally I don’t like the concept of a SynchronizedList very much, because you have to remember to only use it inside a |
That's only needed when iterating through it without the Java forEach method. All other operations are synchronized. |
Yeah, that makes it ever more devious! |
In Kotlin, dealing with nulls works better so we don’t need optional.
The new implementation would throw `ConcurrentModificationExceptions` when destroying the UIs. So let’s play it safe and put the list behind a mutex. Adds a helper class `GuardedByMutex` that can be wrapped around a property to force all use-sites to acquire the lock before doing anything with the data.
cbade0b
to
a7b5f53
Compare
So I’d rather not switch this from a simple Mutex around the list to |
Quality Gate failedFailed conditions See analysis details on SonarQube Cloud Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE |
Note: this conflicts with #9592, so I'd rather merge it after that one is merged back in the refactor branch |
Small helper module
What is it?
Description of the changes in your PR
Before/After Screenshots/Screen Record
Fixes the following issue(s)
Relies on the following changes
APK testing
The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR. You can find more info and a video demonstration on this wiki page.
Due diligence