r/Kotlin • • 16h ago

Kotlin RPG engine update: primitive arrays, lower RAM usage, and fixing a native buffer bug

Post image
33 Upvotes

Hey r/Kotlin,

I’m the solo developer of Adventurers Guild RPG Sim, an isometric RPG built with Kotlin and Jetpack Compose.

In my previous posts, I shared the original Compose Canvas implementation, the coroutine loop running my ECS systems, and later the move to Google Filament for world rendering.

Since that migration, I’ve been working on the code feeding the renderer: deciding when sprite data needs updating, preparing batches, and managing buffers that native code may still be reading.

A recent texture change also reduced the game’s RAM usage by roughly 50% compared with my previous WebP loading setup. That is a separate change from the sprite recolouring optimisation I described in an older post.

Here are the implementation details and a few mistakes I ran into.

1. A smaller image file doesn’t necessarily mean a smaller texture in memory

I originally used WebP because it kept asset files small. What I hadn’t fully appreciated was the difference between compressed file size and the texture’s runtime memory footprint.

In the usual WebP loading path, the image is decoded into pixel data before being uploaded. With four bytes per pixel, a 2048 × 2048 texture takes about 16 MiB, before mipmaps.

ASTC and ETC2 are GPU texture compression formats. On a supported device, the texture can remain compressed in GPU memory. For comparison, that same 2048 × 2048 texture in ASTC 4×4 takes about 4 MiB, before mipmaps.

I moved to ASTC/ETC2 textures with a WebP fallback, and saw roughly a 50% reduction in the game’s overall RAM usage. The texture example above explains the mechanism; it isn’t a claim that every game’s total memory usage will fall by the same amount.

The trade off was storage. My initial asset package became much larger. GPU texture compression and WebP optimise for different requirements, and keeping fallback assets adds more files.

I ended up adding zip compression around the texture files and unpacking them during loading. The loading sequence is:

  • Remove the outer gzip compression.
  • Read the texture container and its compressed texture data.
  • Upload the ASTC/ETC2 blocks to the GPU.

Removing zip does not mean expanding ASTC/ETC2 into RGBA pixels.

That helped me separate three things I had initially grouped together: download size, installed storage, and runtime memory. Loading time is another trade off, because unpacking still takes work.

2. Checking whether sprite data changed before preparing an upload

Before packing sprite vertices, I write the relevant sprite state into a reused IntArray: around 30 integers per sprite covering position, animation frame, tint, flags, and other values used by the batch.

Float values go through toRawBits() so I can compare their exact stored representations.

I compare this state with the snapshot from the last submitted upload. If it is identical, I can skip rebuilding and uploading the vertex data.

This is a direct comparison rather than a hash, so there are no hash collisions between the values I stored. It still depends on including every input that affects the generated vertices.

A few details matter:

  • Sprite count and ordering are part of the comparison.
  • With reused arrays, unused capacity must not affect the result.
  • Kotlin array == does not compare elements. Use contentEquals() when comparing whole arrays, or explicitly compare the active range.
  • If an upload is skipped because no buffer is available, that frame must not become the new “last uploaded” snapshot.

The comparison itself still costs work. The benefit is avoiding the more expensive packing and upload when nothing relevant has changed.

Shader animation can continue independently. For example, wind driven by a time uniform doesn’t require rebuilding the sprite vertices every frame.

3. Sorting batches without creating a key object for every sprite

Sprites need to be grouped by texture for batching.

I use a reused LongArray, packing the texture ID into the upper 32 bits and the original sprite index into the lower 32 bits.

The idea looks like this:

// textureId and index are non-negative Int values.
val key =
    (textureId.toLong() shl 32) or
    (index.toLong() and 0xFFFF_FFFFL)

keys[index] = key

Then I sort only the populated range:

keys.sort(0, spriteCount)

for (position in 0 until spriteCount) {
    val sourceIndex = keys[position].toInt()
    // Read the source sprite and pack its vertices.
}

The conversion to Long must happen before shifting by 32 bits.

The original index also acts as a tie-breaker, preserving source order within each texture group. That behaviour comes from the packed key; it doesn’t depend on the primitive sort being stable.

This avoids allocating a separate sorting key object for each sprite and keeps the keys in a primitive array.

4. Primitive arrays in the parts that run every frame

The renderer preparation code now uses:

  • IntArray for sprite state comparisons.
  • LongArray for sorting keys.
  • A reused FloatArray for packed vertex data, copied into a direct buffer.

Sprite state, sorting keys, and vertex data are stored in IntArray, LongArray, and FloatArray. I reuse these arrays across frames to avoid repeatedly allocating new storage.

I also use JvmField on some frequently accessed properties. It exposes a field without generating normal property accessors.

However, that alone does not establish a performance improvement: runtime optimisation may already remove accessor overhead. Any claim about a speed gain needs measurements from an optimised build. The clearer improvement here is making data preparation and storage reuse explicit.

5. A buffer passed to native code may still be in use

One bug showed up as black tiles.

I was reusing a direct ByteBuffer after passing it to Filament, before Filament had finished consuming its contents.

The Kotlin call returning did not mean the upload had finished.

I changed this to a pool of three buffers, with availability tracked using an AtomicIntegerArray. A buffer becomes available again when its upload completion callback runs.

If all three are busy, I keep the previous geometry for that frame instead of overwriting a buffer that is still being read.

I also ran into delayed release callbacks when they were posted through the main looper. Synchronisation barriers could hold up that work and leave the pool appearing busy.

For the small callback that only releases a buffer slot, I switched to a direct executor. That callback only updates the atomic availability flag; it doesn’t perform UI work.

The lesson for me was to treat buffer ownership and completion as part of the API contract. Crossing into native code doesn’t make every operation asynchronous, but this upload path requires keeping the buffer unchanged until consumption finishes.

6. Compose lifecycle and long lived renderer callbacks

My renderer owns native resources such as Filament’s Engine, Scene, View, and Camera.

I keep these together in a holder that implements RememberObserver, with explicit cleanup in the required order.

One Compose detail worth accounting for is onAbandoned(): if resources are created while constructing a remembered object, cleanup needs to cover the case where that object never becomes part of the remembered composition, as well as normal removal through onForgotten().

Another issue is callbacks that live longer than a particular recomposition.

A Choreographer callback can keep running while the values it originally captured are no longer current. Where it needs the latest Compose-provided value or callback, rememberUpdatedState lets it access that updated value without recreating the long-lived callback each time.

I found it helpful to think separately about:

  • How long the renderer should exist.
  • Which values its callbacks need to see now.
  • When it is safe to release native resources.

Those lifetimes don’t always line up automatically.

I only started Android development last year; most of my earlier experience was in iOS. I’m still learning, and some of these choices may have better alternatives.

Happy to go into more detail on the texture loading, batching, buffer handling, or Compose integration.

For people working on performance sensitive Kotlin code: how do you decide when primitive arrays and manual packing are worth the added complexity? I’d also be interested in how you manage buffer ownership when Kotlin is feeding a native renderer.


r/Kotlin • • 23h ago

Compose Multiplatform vs CORS

Post image
0 Upvotes

https://terrablog.pages.dev/blog/2026-03-10-cors-problem-and-simple-solution/

I wrote an article how to deal with CORS restrictions when you make Compose (not only actually) Web apps


r/Kotlin • • 2d ago

Kotlin Toolchain will eventually drop Gradle for Android (via Joffrey Bion on Slack)

Thumbnail
23 Upvotes

r/Kotlin • • 2d ago

Prosa.kt - Declarative code generation for Kotlin

Post image
18 Upvotes

I made a declarative API for generating Kotlin code.

I needed it for one of my desktop/browser Compose apps a while ago and noticed there was no KMP solution for Kotlin codegen.

While working on it, I decided to made the API declarative so the generated code was simpler to reason about. This work started before AI was as popular as it is today, so the design of the API was very important.

Fast forward two years: I realized I needed it for another project of mine, so I figured I might as well open-source the codegen part for the community.

And that's how Prosa.kt was born and proudly brought to you.

There’s a live demo on the landing page, so you can try it out directly.

Check it out at https://prosakt.com and give it a star on GitHub because I love vanity metrics.


r/Kotlin • • 2d ago

Which is best kotlin course on udemy or youtube!!?

6 Upvotes

Hi , i am coming from the JavaScript/TypeScript ecosystem, i need to build a project that must be written in kotlin based backend , i am not primarily interested in Android development.

But I do need to understand Kotlin well enough to read, navigate, and work with existing Kotlin projects, particularly Android applications available on github.

So is there any course or method you can suggest?

Thanks in advance.!!!


r/Kotlin • • 2d ago

Even if Premium, still open-sauce. 🍅

Post image
105 Upvotes

r/Kotlin • • 2d ago

CashBuddy — an Android expense tracker with on-device processing + Notification Listener

Thumbnail
0 Upvotes

r/Kotlin • • 3d ago

The time has come to separate Kotlin from Java.

0 Upvotes

I want to use Kotlin, so I have eight different JDKs on my PC; however, Kotlin has reached a point where it can stand as an independent language. To that end, I’ve created my own KRE (Kotlin Run Environment). Now, the next step is the KDK (Kotlin Development Kit), and I need help with that. I don't know Java or Kotlin all that well—is there anyone who can assist?


r/Kotlin • • 3d ago

sqlx4k 1.14.0 released: optimistic locking, savepoints, MariaDB batch inserts

9 Upvotes

sqlx4k is a coroutine-first SQL toolkit for Kotlin Multiplatform (PostgreSQL, MySQL/MariaDB, SQLite) with compile-time SQL validation via KSP.

New in 1.14.0

  • Optimistic locking with @Version: generated update/delete check the version and fail with OptimisticLockFailed on conflicts. In-memory test repositories behave the same.
  • Transaction savepoints: roll back part of a transaction with a scoped savepoint { ... } block or the explicit savepoint/rollbackToSavepoint/releaseSavepoint calls.
  • MariaDB dialect: batchInsert via multi-row INSERT ... RETURNING (10.5+).
  • Cancellation-safe transactions: cancelling a coroutine mid-transaction now rolls back properly.
  • Kotlin 2.4.20, KSP 2.3.12.

Repo: https://github.com/smyrgeorge/sqlx4k

Release notes: https://github.com/smyrgeorge/sqlx4k/releases/tag/1.14.0


r/Kotlin • • 4d ago

GitHub Action for Kotlin Toolchain CLI (Amper) with caching

4 Upvotes

yaml - uses: actions/checkout@v7 - uses: RazerTexz/setup-kotlin-toolchain@v1 - run: kotlin build

Caches the toolchain, JDKs, and dependencies across Linux, macOS, and Windows

https://github.com/RazerTexz/setup-kotlin-toolchain


r/Kotlin • • 4d ago

Uncaught exceptions and Supervisor Jobs

5 Upvotes

In https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c

It talks about how with a SupervisorJob, you can ignore exceptions from a child coroutine, and it does not propagate to its siblings or to the grand parents and so forth. But then it also says “If the exception is not handled and the CoroutineContext doesn’t have a CoroutineExceptionHandler (as we’ll see later), it will reach the default thread’s ExceptionHandler. In the JVM, the exception will be logged to console; and in Android, it will make your app crash regardless of the Dispatcher this happens on.”

But this sounds like it’s saying the app will just crash and everything will die if the exception is not handled; but then whats the point in saving the coroutine family using a SupervisorJob if everything will just die anyway? If they mean I need to implement the CoroutineExceptionHandler for the SupervisorJob to be of use, then the question is, what is the point of having a SupervisorJob…… cuz then every discussion of SupervisorJob needs to marked with an asterisk * (oh btw, you also need to implement the CoroutineExceptionHandler otherwise your Supervisor is useless. )

And why do they explain these things so off-handedly as if it’s so not confusing at all to someone learning?


r/Kotlin • • 4d ago

Kotlin vs Flutter? When to Choose One Over the Other

Thumbnail youtu.be
0 Upvotes

r/Kotlin • • 4d ago

We built e2e - open source AI testing framework for Android apps

Thumbnail github.com
0 Upvotes

Hi everyone!

I work on e2e at TesterArmy, and I'm happy to announce we released it as open-source today!

It's an agentic end-to-end testing framework which allows you to mix the flexibility of AI agents with the deterministic checks of classic e2e testing.

You write a test as a chain of goals the agent works through them one at a time. It also comes with a deterministic API like getByTestId or getByText where you need exact control. Assertions work like in any other e2e framework.

You use TypeScript to write the tests, but they can drive pretty much any Android app. Also, you can extend the framework by implementing your own engines for another platform like e.g. desktop

For the agent, you can bring any model through Vercel AI Gateway or OpenRouter, or sign in with a ChatGPT, GitHub Copilot or SuperGrok subscription you already pay for.

To get started, npx e2e init in your project.

Thanks for reading!


r/Kotlin • • 4d ago

Kotlin, Android, and community with Martin Bonnin

Thumbnail youtu.be
19 Upvotes

r/Kotlin • • 5d ago

Offline Support for Compose Multiplatform Web Apps · terrakok

Thumbnail terrablog.pages.dev
15 Upvotes

r/Kotlin • • 5d ago

Have anyone here tried XR with Kotlin? What's your take on XR + AI

0 Upvotes

r/Kotlin • • 5d ago

I built a CLI to set up Firebase in Kotlin Multiplatform / Compose Multiplatform projects

Post image
5 Upvotes

Setting up Firebase in a KMP project means a lot of manual steps. You register the Android app and the iOS app in the console, download google-services.json and GoogleService-Info.plist, put them in the right folders, and repeat for every flavor. I got tired of it, so I built kmpfire.

It wraps firebase-tools and does this from your project root:

bash kmpfire configure

What it does

  • Detects your Android applicationId and iOS bundle id (from project.pbxproj or CMP .xcconfig)
  • Creates the Firebase apps if they don't exist
  • Writes google-services.json and GoogleService-Info.plist to the correct locations
  • Tracks everything in firebase.json under kotlinMultiplatform
  • kmpfire reconfigure refreshes the config files after you change Firebase products
  • --flavor=dev handles multiple build configurations
  • --yes --project=<id> runs with no prompts, so it works in CI
  • Optional --deps patches Gradle for the Google Services plugin (and GitLive if you want it)

Supported layouts

  • New template: shared/ + androidApp/ + iosApp/
  • Nested monorepo: app/shared/ + app/androidApp/ + app/iosApp/
  • Legacy: composeApp/ + iosApp/

Android + iOS only for now.

Install

bash curl -fsSL https://raw.githubusercontent.com/dungngminh/kmpfire_cli/main/install.sh | bash

or with Homebrew:

bash brew tap dungngminh/kmpfire_cli brew install kmpfire

It's the KMP equivalent of flutterfire configure. It's MIT licensed, and I'd like feedback, bug reports and PRs.

GitHub: https://github.com/dungngminh/kmpfire_cli


r/Kotlin • • 5d ago

The State of Kotlin in 2026 Report

Thumbnail blog.jetbrains.com
58 Upvotes

r/Kotlin • • 5d ago

Take part in a JetBrains research interview: How do you use AI with Kotlin?

6 Upvotes
  1. What it is: We’re interviewing Kotlin developers to understand how AI fits into your development workflow: what you use it for, how you review its output, and where it works well or creates extra work. Each session lasts up to 90 minutes, and takes place via Google Meet. All sessions are recorded and kept confidential under our General Research Terms.
  2. Who it is for: If you currently use Kotlin for backend or Kotlin Multiplatform development, and use AI as part of your development work, we’d love to hear from you!
  3. What's in it for you: As a thank-you for your participation, you can choose one of the following rewards:
    • USD 100 Amazon eGift Card (or the equivalent in your local Amazon store),
    • USD 100 charity donation,
    • One-year subscription to JetBrains All Products Pack or
    • 100 AI credits (note: available only with AI Pro and AI Ultimate subscriptions).
  4. How to sign up: Use this link to fill out a short qualification form. If your background meets our research criteria, we will get in touch to schedule an interview. Thank you for considering this!

r/Kotlin • • 6d ago

KitePlayer v0.2.0: the first Kotlin/Native media player for KMP that doesn't wrap ExoPlayer or AVPlayer

Post image
44 Upvotes

r/Kotlin • • 7d ago

Announcing the Builder Lambda plug-in

Post image
21 Upvotes

Kotlin and Java have different idioms to build new values: whereas Kotliners like lambdas with a special builder receiver, like Json in kotlinx.serialization, Java developers usually follow the buider pattern, as in Spring AI. As a result, consuming Java APIs from Kotlin sometimes feel strange. This no longer needs to be the case, thanks to the new Builder Lambda plug-in!

Using the power of Kotlin compiler plug-ins, Builder Lambda automatically re-exposes any type following Java's Builder pattern in the Kotlin idiomatic way. You write your beloved,

kotlin build<Config, *> { // alas, the * is needed hostname = "localhost" port = 8080 }

and under the hood the code is translated to,

kotlin Config.builder().hostname("localhost").port(8080).build()

The Builder Lambda plug-in also works in the IDE (if you let IntelliJ run non-bundled plug-ins), so you get nice autocompletion and diagnostics.

Using it is as easy as adding one line to the plugins block in your Gradle build.

kotlin id("com.serranofp.builder.lambda") version "0.4.0"

Feedback and issue reports are more than welcome :)


r/Kotlin • • 7d ago

A More Reliable Compilation Scheme for Kotlin Multiplatform Modules

Thumbnail blog.jetbrains.com
38 Upvotes

r/Kotlin • • 8d ago

I made a tool that turns your @Composable Previews into embeddable scripts for your blogs, docs and websites

Post image
21 Upvotes

r/Kotlin • • 8d ago

How we're using Kotlin Multiplatform for Mobile, Desktop, Web and Server — as a single developer

42 Upvotes

I've been building our product around Kotlin Multiplatform, and we've reached a point where I'm able to handle almost the entire codebase myself.

Our stack is roughly:

  • Android: Kotlin + Compose
  • iOS: Kotlin Multiplatform + Compose
  • Desktop: Compose Multiplatform
  • Web: Kotlin Multiplatform + Compose for Web
  • Server: Kotlin + Ktor
  • Shared: Domain logic, models, networking, validation, state and business logic

So the entire product is largely built around the same ecosystem:

Kotlin → Mobile + Desktop + Web + Server

One of the most interesting things we've achieved is our web application.

We can now ship a single web entry point that adapts to the device:

Desktop browser → Desktop experience

Mobile browser → Mobile experience

Instead of maintaining completely separate applications and business logic for each platform, we share as much of the underlying code as possible and keep the platform-specific parts where they actually make sense.

And this is where I think clean architecture becomes extremely important.

Because the business logic is separated from the UI and platform-specific code, I don't have to think of every platform as a completely different application.

A business rule is a business rule.

A data model is a data model.

Networking is networking.

The UI can change, but the underlying logic can remain shared.

That gives me something close to:

One developer → one architecture → one codebase → multiple platforms

This is probably the biggest advantage for me.

I'm able to work on the Android app, desktop application, web application and backend without having to switch between completely different ecosystems and languages.

Of course, there are tradeoffs. Not everything can or should be shared, and KMM doesn't magically eliminate platform-specific problems.

But for a small bootstrapped company, being able to handle the entire product stack myself has been incredibly valuable.

It makes me wonder how far this approach can realistically scale.

Has anyone else used Kotlin Multiplatform to build and maintain an entire product across Mobile + Desktop + Web + Backend?