-
Notifications
You must be signed in to change notification settings - Fork 0
§3. Chameleon
Stripping is great when a declaration belongs to one platform. But a lot of shared code is the same logic written twice only because the platforms spell a type or an accessor differently:
// The duplication @Chameleon removes — identical intent, two platform dialects:
@PaperOnly fun resetSelection(player: Player) = resetSelection(player.uniqueId)
@FabricOnly fun resetSelection(player: ServerPlayer) = resetSelection(player.uuid)A @Chameleon carrier is a platform-annotated declaration that codegen resolves to a single real declaration for the active target. Put the carriers in src/main/chameleons (they are read by codegen, never compiled directly):
// src/main/chameleons/Platform.kt
import io.github.arnodoelinger.platformweaver.*
// Type alias — erases type-name divergence:
@PaperOnly @Chameleon val PlatPlayer: org.bukkit.entity.Player
@FabricOnly @Chameleon val PlatPlayer: net.minecraft.server.level.ServerPlayer
// Extension property — erases accessor divergence (a type alias can't do this):
@PaperOnly @Chameleon val PlatPlayer.platUuid: java.util.UUID get() = uniqueId
@FabricOnly @Chameleon val PlatPlayer.platUuid: java.util.UUID get() = uuidNow the two functions above collapse into one, written once for every platform:
fun resetSelection(player: PlatPlayer) = resetSelection(player.platUuid)A carrier is a single, expression-bodied line. Three shapes are supported:
| Carrier | Generated for the target |
|---|---|
val Name: Type (bare type) |
typealias Name = Type |
val [Recv.]name: T get() = e (has a body) |
the property, verbatim |
fun [Recv.]name(p): T = e |
the function, verbatim |
A bare val Name: Type becomes a typealias; anything with a receiver or a body is emitted verbatim for the active platform.
Member bodies can use anything imported in the carrier file. Codegen carries an import into the generated file only when the emitted declaration references it, so:
- The type-alias form keeps its guarantee that a non-target platform's API need not be on the classpath, and
- A member body can naturally reference helpers (e.g.
Main.config) without fully-qualifying them.
The carrier directory defaults to src/main/chameleons. Change or disable it via the Gradle DSL:
platformweaver {
target = "paper"
chameleonsDir = "src/main/chameleons" // Set to null to disable chameleon codegen
}@Chameleon handles type and accessor divergence. When two platforms hand you structurally different data — Paper's Location bundles the world, while Fabric splits it into BlockPos + a world key — that's a real API difference, not a naming one. Normalize it once behind a small adapter type (see Same-name declarations), then chameleon that.