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

Player UI list to kotlin #11829

Open
wants to merge 3 commits into
base: refactor
Choose a base branch
from

Conversation

Profpatsch
Copy link
Contributor

@Profpatsch Profpatsch commented Dec 26, 2024

Small helper module

What is it?

  • Bugfix (user facing)
  • Feature (user facing)
  • Codebase improvement (dev facing)
  • Meta improvement to the project (dev facing)

Description of the changes in your PR

  • record videos
  • create clones
  • take over the world

Before/After Screenshots/Screen Record

  • Before:
  • After:

Fixes the following issue(s)

  • Fixes #

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

@github-actions github-actions bot added the size/medium PRs with less than 250 changed lines label Dec 26, 2024
And simplify the code a little
@Profpatsch Profpatsch force-pushed the PlayerUIList-to-kotlin branch 2 times, most recently from 2e23e77 to 5b92de4 Compare December 26, 2024 11:50
@ShareASmile ShareASmile added codequality Improvements to the codebase to improve the code quality rewrite Issues and PRs related to rewrite labels Dec 26, 2024
@github-actions github-actions bot added size/large PRs with less than 750 changed lines and removed size/medium PRs with less than 250 changed lines labels Dec 26, 2024
@Profpatsch Profpatsch force-pushed the PlayerUIList-to-kotlin branch from 15d5174 to 15eca50 Compare December 26, 2024 14:46
@Profpatsch
Copy link
Contributor Author

Okay I noticed a funny thing where threads would compete for modifying our UI list, so I added a Mutex around it.

@Profpatsch Profpatsch force-pushed the PlayerUIList-to-kotlin branch 4 times, most recently from 2f32929 to cbade0b Compare December 26, 2024 14:53
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@Profpatsch
Copy link
Contributor Author

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

@ShareASmile ShareASmile added the player Issues related to any player (main, popup and background) label Jan 23, 2025
Copy link
Member

@Isira-Seneviratne Isira-Seneviratne left a 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.

Comment on lines +115 to +123
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)
}
}
Copy link
Member

@Isira-Seneviratne Isira-Seneviratne Jan 24, 2025

Choose a reason for hiding this comment

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

Suggested change
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)
}

Copy link
Member

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?

Copy link
Member

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.

Copy link
Member

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.

Comment on lines +88 to +97
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
Copy link
Member

@Isira-Seneviratne Isira-Seneviratne Jan 24, 2025

Choose a reason for hiding this comment

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

Suggested change
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>())
Copy link
Member

@Isira-Seneviratne Isira-Seneviratne Jan 24, 2025

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.

Suggested change
var playerUis = GuardedByMutex(mutableListOf<PlayerUi>())
val playerUis = Collections.synchronizedList(mutableListOf<PlayerUi>())

@Stypox
Copy link
Member

Stypox commented Jan 25, 2025

The rest looks good to me, too, thanks!

Comment on lines +58 to +77
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()
}
Copy link
Member

@Isira-Seneviratne Isira-Seneviratne Jan 25, 2025

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:

Suggested 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()
}

@Profpatsch
Copy link
Contributor Author

@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?

@Isira-Seneviratne
Copy link
Member

@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.

@Profpatsch
Copy link
Contributor Author

Personally I don’t like the concept of a SynchronizedList very much, because you have to remember to only use it inside a synchronized block, otherwise you introduce bugs. But nothing will remind you to do that.

@Isira-Seneviratne
Copy link
Member

Isira-Seneviratne commented Jan 27, 2025

Personally I don’t like the concept of a SynchronizedList very much, because you have to remember to only use it inside a synchronized block, otherwise you introduce bugs. But nothing will remind you to do that.

That's only needed when iterating through it without the Java forEach method. All other operations are synchronized.

@Profpatsch
Copy link
Contributor Author

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.
@Profpatsch Profpatsch force-pushed the PlayerUIList-to-kotlin branch from cbade0b to a7b5f53 Compare January 27, 2025 12:52
@Profpatsch
Copy link
Contributor Author

So I’d rather not switch this from a simple Mutex around the list to synchronizedList, mainly because I don’t think having a partially thread-safe list really makes the implementation simpler, it’s just harder to reason about and easy to make mistakes.

Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@Stypox
Copy link
Member

Stypox commented Jan 27, 2025

Note: this conflicts with #9592, so I'd rather merge it after that one is merged back in the refactor branch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
codequality Improvements to the codebase to improve the code quality player Issues related to any player (main, popup and background) rewrite Issues and PRs related to rewrite size/large PRs with less than 750 changed lines
Projects
Status: Todo
Development

Successfully merging this pull request may close these issues.

4 participants