-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Initial Event Bus implementation & Event-based multiplayer updates #6826
Initial Event Bus implementation & Event-based multiplayer updates #6826
Conversation
078a5a6
to
f6a4d5f
Compare
@@ -127,4 +129,5 @@ dependencies { | |||
// run `./gradlew build --scan` to see details | |||
implementation("androidx.core:core-ktx:1.6.0") | |||
implementation("androidx.work:work-runtime-ktx:2.6.0") | |||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") |
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 increases the apk size by 3.431kB, but gives us almost all newer Java APIs even on old devices. I mainly included this for java.time
(kotlin.time
seems to basically only be for performance testing/debugging).
I can remove this if this size increase is not acceptable, it does increase the apk size by roughly 30% from around 11.5 to around 15 MB, but I would rather not :D me likey newey featurey
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 remember arguing over desugaring once. When Unciv wanted to bump its minimum android sdk version. But it was shot down.
delay(getRefreshInterval().toMillis()) | ||
|
||
// TODO will be used later | ||
// requestUpdate() |
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'll be used when my ingame multiplayer status display is done.
fun GameInfoPreview.isUsersTurn() = getCivilization(currentPlayer).playerId == UncivGame.Current.settings.userId | ||
fun GameInfo.isUsersTurn() = getCivilization(currentPlayer).playerId == UncivGame.Current.settings.userId |
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 was thinking of putting these into the actual classes, but they are solely multiplayer functions. I think it's good for them to be here.
|
||
|
||
/** @see getUpdateThrottleInterval */ | ||
private const val DROPBOX_THROTTLE_INTERVAL = 8L |
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 means that when a user spams the "Refresh list" button, they will only get an update every 8 seconds of doing that.
I think this is more than acceptable with our rate limiting issues there.
} | ||
private class GameList( | ||
onSelected: (String) -> Unit | ||
) : VerticalGroup() { |
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 know Table
are being used mostly, and I now understand why. But in this case, a VerticalGroup
makes much more sense, because it is able to remove/add and correctly layout actors all the time. With a table, removing/editing a cell is possible, but annoying, it doesn't happen automatically. Also it has the nice benefit of allowing us to do children.sort()
to have the games always listed in alphabetical order.
val turnIndicator = createIndicator("OtherIcons/ExclamationMark") | ||
val errorIndicator = createIndicator("StatIcons/Malcontent") | ||
val refreshIndicator = createIndicator("EmojiIcons/Turn") | ||
val statusIndicators = HorizontalGroup() |
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.
Same with the VerticalGroup
comment above. Much better remove/add handling.
|
||
private fun createIndicator(imagePath: String): Actor { | ||
val image = ImageGetter.getImage(imagePath) | ||
image.setSizeForced(50f) |
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 can imagine this was one of the main problems when trying to use anything other than Table
. This makes it nice and dandy though. (See later comment)
* Either [Image] or [com.badlogic.gdx.scenes.scene2d.utils.Drawable] is badly written, as the [Image.drawable] always has its texture size as min size, | ||
* and [Image] pref size is always equal to its [Image.drawable]'s min size. |
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 is the crux of the issue with Image
s & putting them in anything else than Table
s. All the WidgetGroup
s like the horizontal/vertical one use the child actor's prefWidth/Height
to set the size. Because of the design flaw outlined in this method comment, doing Image.setSize
does absolutely nothing since the Drawable
s minWidth/Height
is used as prefWidth/Height
, and the actual width/height
properties are completely ignored for prefWidth/Height
... utter stupidity.
Table
actually ignores the prefWidth/Height
of any actors within cells if you do Cell.size(..)
, which is why that works so nicely. But there's no reason why the other WidgetGroup
s shouldn't be used, they do have their place & time.
} catch (ex: FileStorageRateLimitReached) { | ||
postCrashHandlingRunnable { | ||
val cantUploadNewGamePopup = Popup(this@WorldScreen) | ||
cantUploadNewGamePopup.addGoodSizedLabel("Server limit reached! Please wait for [${ex.limitRemainingSeconds}] seconds").row() | ||
cantUploadNewGamePopup.addCloseButton() | ||
cantUploadNewGamePopup.open() | ||
} |
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 addition to reduced code duplication, this is also a bugfix: in the other catch (ex: Exception)
branch is where isPlayersTurn
and shouldUpdate
are set to true
, but not in this branch.
Also,
is not adjusted on purpose, since that will also be changed with the ingame multiplayer status display. |
// Game is so old that a preview could not be found on dropbox lets try the real gameInfo instead | ||
gamePreview = OnlineMultiplayerGameSaver().tryDownloadGame(gameId).asPreview() | ||
fileHandle = GameSaver.saveGame(gamePreview, saveFileName) |
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 was added on 2021-12-13, can this be removed? How long until old saves on Dropbox get deleted?
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.
One of the open problems around here: No auto cleanup on "Unciv's" Dropbox.
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 manually remove a lot of them every time the dropbox fills up. Currently, all save games up to (but excluding) 2021-10-31 are removed from the dropbox
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.
Ah yes there was some python script and idea to make it a github action...
This would have been a nice way to track which cities need to re-evaluate in my PR #6650 |
And this is exactly why I want to add this. It's so incredibly useful. |
…ates event-based instead of throwaway+rebuild
f6a4d5f
to
56323f3
Compare
I rebased this branch on master - no conflicts |
Six pages essentially a monologue so far... OK I'll read the beast.
OK, this is key. In fact, I must find an excuse to disparage a 16-char oneliner.... ... .. . OK: Probability for Name conflicts with std libraries too high? Auto-Imports of Group, Array, Timer etc all run into problems that maybe the wrong one is chosen... Extra namespace "EventBus.Event" (need for qualifier can be import-tweaked away if needed - per-file)? "UncivEvent" (too long)?. "Efent" (:rofl:)? We'll probably just have to be careful. Oh, and please end all new files with a LF for prettier github rendering.
There's no kotlin equivalent? Even if it turns out to be just a typealias...
This does not actually receive, right? It's more a listen to or register handler or somesuch. Oh, private, the meat is elsewhere. never mind - yes, the dox of the actual meat make it clear. (Besides, isn't the 'recieve' typo obligatory by now?)
'Errored' - uh, okay, can't think of a better name either. And - why is Event an open class instead of an interface anyway? Would save Gigabytes of At the UI stuff I started to skip stuff, trust will do. |
Failed? |
Good ™️ for you. When asked that I always faint. 👽 👨⚕️
We had precedences. Java math where using kotlin's consistently would be better, and one with just a tiny little more weight I forget? And for some Gdx classes I always think "does everybody on the project know not to pick the first offer"? Not important.
That one had a link. Duckduck instead of Studio. May be out of context, didn't really dive in. |
That link is to https://kotlinlang.org/docs/native-overview.html |
Ah, out of context. Got it. 🤦♀️ |
After adding the possibility to filter events, I forgot to add that to the |
Found another issue - the |
Not really part of Event Bus, right? .... Split it off? |
# Conflicts: # core/src/com/unciv/ui/multiplayer/MultiplayerScreen.kt
@SomeTroglodyte I resolved the conflict, it was just an unused method removal from the warnings cleanup. |
@yairm210 did you want to look at this still? I implemented your request of the event bus staying mostly async-free, i.e. it's only ran from a single thread. Unfortunately if you already have code that is asynchronous you can't get around some debug issues where you can't quite step through everything, because you already are starting other threads. Otherwise I don't think anyone else will be looking at this soon so I'd merge it :D |
I see the merge was easy, yes. I reaffirm I think this will be a boon - we'll just have to get people to actually use it. Recalc stats, reassign population,... Merge with just our two votes? Or request a review from someone specific first? |
Yeah I think I'll merge, I want that other stuff that is based on this in here faster :) if @yairm210 has any concerns we can still make changes after :) |
I think that once we start implementing things using this, we should add tests to validate previous behavior, and ensure that that behavior remains after refactor |
Looks like I didn't read properly ¯\_(ツ)_/¯ |
Just testing Github's email capabilities.
Also what is this event bus for?
Will it trigger an event for every change in GameInfo so that we can handle
those events or something like that?
I mean what does it cover???
…On Sun, May 22, 2022, 04:10 Yair Morgenstern ***@***.***> wrote:
I think that once we start implementing things using this, we should add
tests to validate previous behavior, and ensure that that behavior remains
after refactor
—
Reply to this email directly, view it on GitHub
<#6826 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/ALDVLCX5W6MY77OGMYIOOC3VLFNN7ANCNFSM5V7XJETQ>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Just testing Githubs email capabilities.
Also what is this event bus for?
Will it trigger an event for every change inside a game so that we can
handle the even and something like that?
|
Whatever you define as an event |
This is still just another PR in preparation for the feature I actually want to add :D
In total, this is basically just a refactoring, there should be basically no observable changes, except where otherwise noted.
A central event bus is added. It is based on standard kotlin classes that extend
Event
. This imo makes it very nice to work with since you can add any data with the event you like, immediately with no boilerplate code. You also can see all events just by telling your IDE to list all implementing classes. In addition, it was requested that it not be asynchronous/threaded for better debuggability, so you are only supposed to use it on the main render thread. This thread was chosen because when you listen for events, you mostly want to do something with the UI immediately based on the event. Otherwise I hope it's pretty simple & self-explanatory.(This "non-multithreaded requirement" also means I can't show off very nice coroutine features :D the whole
EventBus
file could be shrinked to 10 lines with added asynchronous support, at the expense of being able to step through from send->receive in the debugger)The other part of this PR is a major refactor of the multiplayer logic to be centralized in their own classes in
com.unciv.logic.multiplayer
instead of being strewn around variousScreen
s. And instead of using the "throw away everything and rebuild based on the newest data"-approach that has mostly been used so far, it instead now uses events from the event bus to listen for changes and only update the specific components that need updating.(This is not really noticeable in the
MultiplayerScreen
because not much is happening there, but when you extend this approach to more performance-critical parts of the game, this will get us major performance benefits. We currently "if something changed, throw away things and rebuild based on the newest data" a lot of things when aWorldScreen.update
happens - if all those things would not need a "has updated" check and only update the small parts that actually change, the whole game would be lightning-fast)More possibly interesting comments as code comments down below: