godot.runtime.Registration.kt Maven / Gradle / Ivy
Show all versions of godot-runtime Show documentation
package godot.runtime
import godot.core.*
import godot.util.camelToSnakeCase
import kotlin.reflect.*
class KtPropertyInfoBuilderDsl {
var type: VariantType? = null
var name: String = ""
var className: String? = null
var hint: PropertyHint = PropertyHint.NONE
var hintString: String = ""
var rpcModeId: Int = 0
internal fun build() = KtPropertyInfo(checkNotNull(type), name, checkNotNull(className), hint, hintString, rpcModeId)
}
data class KtFunctionArgument(
val type: VariantType,
val className: String,
val name: String = "" //empty for return type
) {
internal fun toKtPropertyInfo() = KtPropertyInfo(
type,
name,
className,
PropertyHint.NONE,
"", //always empty. Only used for properties
0 // always RPCMode.DISABLED. Only used for properties
)
}
class ClassBuilderDsl(
@PublishedApi internal val name: String,
private val registeredName: String,
private val superClass: String,
private val baseGodotClass: String
) {
private val constructors = mutableMapOf>()
private val functions = mutableMapOf>()
@PublishedApi
internal val properties = mutableMapOf>()
private val signals = mutableMapOf()
fun constructor(constructor: KtConstructor) {
require(!constructors.containsKey(constructor.parameterCount)) {
"A constructor with ${constructor.parameterCount} argument(s) already exists."
}
require(constructor.parameterCount <= CONSTRUCTOR_MAX_ARGS) {
"Cannot register a constructor with ${constructor.parameterCount} arguments, max argument count is $CONSTRUCTOR_MAX_ARGS"
}
constructors[constructor.parameterCount] = constructor
}
fun property(
kProperty: KMutableProperty1,
variantType: VariantType,
type: VariantType,
className: String,
hint: PropertyHint = PropertyHint.NONE,
hintString: String = "",
defaultArgument: P?,
rpcModeId: Int = 0,
isRef: Boolean = false
) {
val propertyName = kProperty.name.camelToSnakeCase()
require(!properties.contains(propertyName)) {
"Found two properties with name $propertyName for class $name"
}
properties[propertyName] = KtProperty(
KtPropertyInfo(
type,
propertyName,
className,
hint,
hintString,
rpcModeId
),
kProperty,
variantType,
defaultArgument,
isRef
)
}
inline fun > enumProperty(
kProperty: KMutableProperty1,
defaultValue: P,
rpcModeId: Int = 0
) {
val propertyName = kProperty.name.camelToSnakeCase()
require(!properties.contains(propertyName)) {
"Found two properties with name $propertyName for class $name"
}
properties[propertyName] = KtEnumProperty(
KtPropertyInfo(
VariantType.LONG,
propertyName,
"Int",
PropertyHint.ENUM,
enumValues().joinToString { it.name },
rpcModeId
),
kProperty,
//TODO change when nullable enum are here.
defaultValue,
{ enum: P? -> enum?.ordinal ?: 1 },
{ i -> enumValues
()[i] }
)
}
//TODO: uncomment and fixup once collections are supported in KtVariant
// inline fun > enumListProperty(
// kProperty: KMutableProperty1>
// ) {
// val propertyName = kProperty.name.camelToSnakeCase()
// require(!properties.contains(propertyName)) {
// "Found two properties with name $propertyName for class $name"
// }
//
// properties[propertyName] = KtProperty(
// KtPropertyInfo(
// VariantType.LONG,
// propertyName,
// "Int",
// PropertyHint.ENUM,
// "2/3:${enumValues().joinToString(",") { it.name }}" //2 = VariantType.LONG.ordinal | 3 = PropertyHint.ENUM.ordinal
// ),
// kProperty,
// { enumList ->
// KtVariant(enumList.map { it.ordinal } as Collection)
// },
// { ktVariant -> enumValues
()[ktVariant.asInt()] }
// )
// }
@JvmName("enumFlagPropertyMutable")
inline fun > enumFlagProperty(
kProperty: KMutableProperty1>,
defaultValue: MutableSet,
rpcModeId: Int
) = enumFlagProperty(kProperty as KMutableProperty1>, defaultValue, rpcModeId)
inline fun > enumFlagProperty(
kProperty: KMutableProperty1>,
defaultValue: Set,
rpcModeId: Int
) {
val propertyName = kProperty.name.camelToSnakeCase()
require(!properties.contains(propertyName)) {
"Found two properties with name $propertyName for class $name"
}
properties[propertyName] = KtEnumProperty(
KtPropertyInfo(
VariantType.LONG,
propertyName,
"Int",
PropertyHint.FLAGS,
enumValues
().joinToString { it.name },
rpcModeId
),
kProperty,
//TODO : Change when null default values are supported
defaultValue,
{ enumSet ->
var intFlag = 0
enumSet?.forEach { enum ->
intFlag += 1 shl enum.ordinal
}
intFlag
},
{ value ->
val intFlag = (value as P).ordinal
val enums = mutableSetOf
()
var bit = 1
for (i in 0 until Int.SIZE_BITS) {
if ((intFlag and bit) > 0) {
val element = enumValues
().firstOrNull { it.ordinal == i }
if (element != null) {
enums.add(element)
}
}
bit = bit shl 1
if (bit > intFlag) break
}
enums
}
)
}
fun property(
kProperty: KMutableProperty1,
variantType: VariantType,
setValueConverter: ((Any?) -> P),
isRef: Boolean = false,
defaultArgument: P,
rpcModeId: Int = 0,
pib: KtPropertyInfoBuilderDsl.() -> Unit
) {
val builder = KtPropertyInfoBuilderDsl()
builder.name = kProperty.name.camelToSnakeCase()
builder.rpcModeId = rpcModeId
builder.pib()
val property = builder.build()
require(!properties.contains(property.name)) {
"Found two properties with name ${property.name} for class $name"
}
properties[property.name] = KtProperty(property, kProperty, variantType, defaultArgument, isRef)
}
fun function(
func: KFunction1,
variantType: VariantType,
returnType: KtFunctionArgument,
rpcModeId: Int = 0
) {
appendFunction(
KtFunction0(
KtFunctionInfo(
func.name.camelToSnakeCase(),
listOf(),
KtPropertyInfo(
returnType.type,
"",
returnType.className,
PropertyHint.NONE,
"",
0 // always RPCMode.DISABLED. Only used for properties
),
rpcModeId
),
func,
variantType
)
)
}
fun function(
func: KFunction1,
variantType: VariantType,
returns: KtPropertyInfoBuilderDsl.() -> Unit,
rpcModeId: Int = 0
) {
val returnBuilder = KtPropertyInfoBuilderDsl()
returnBuilder.returns()
appendFunction(
KtFunction0(
KtFunctionInfo(func.name.camelToSnakeCase(), listOf(), returnBuilder.build(), rpcModeId),
func,
variantType
)
)
}
fun function(
func: KFunction2,
variantType: VariantType,
p0Type: Pair,
p0: KtFunctionArgument,
returnType: KtFunctionArgument,
rpcModeId: Int = 0
) {
appendFunction(
KtFunction1(
KtFunctionInfo(
func.name.camelToSnakeCase(),
listOf(
p0.toKtPropertyInfo()
),
returnType.toKtPropertyInfo(),
rpcModeId
),
func,
variantType,
p0Type
)
)
}
fun function(
func: KFunction2,
variantType: VariantType,
p0Type: Pair,
arg: KtPropertyInfoBuilderDsl.() -> Unit,
returns: KtPropertyInfoBuilderDsl.() -> Unit,
rpcModeId: Int = 0
) {
val (arguments, returnType) = argumentsAndReturnType(returns, arg)
appendFunction(
KtFunction1(
KtFunctionInfo(func.name.camelToSnakeCase(), arguments, returnType, rpcModeId),
func,
variantType,
p0Type
)
)
}
fun function(
func: KFunction3,
variantType: VariantType,
p0Type: Pair,
p1Type: Pair,
p0: KtFunctionArgument,
p1: KtFunctionArgument,
returnType: KtFunctionArgument,
rpcModeId: Int = 0
) {
appendFunction(
KtFunction2(
KtFunctionInfo(
func.name.camelToSnakeCase(),
listOf(
p0.toKtPropertyInfo(),
p1.toKtPropertyInfo(),
),
returnType.toKtPropertyInfo(),
rpcModeId
),
func,
variantType,
p0Type,
p1Type
)
)
}
fun function(
func: KFunction3,
variantType: VariantType,
p0Type: Pair,
p1Type: Pair,
args: Array Unit>,
returns: KtPropertyInfoBuilderDsl.() -> Unit,
rpcModeId: Int = 0
) {
val (arguments, returnType) = argumentsAndReturnType(returns, *args)
require(args.size == 2) {
"Function ${func.name.camelToSnakeCase()} should have 2 arguments, found ${args.size}"
}
appendFunction(
KtFunction2(
KtFunctionInfo(func.name.camelToSnakeCase(), arguments, returnType, rpcModeId),
func,
variantType,
p0Type,
p1Type
)
)
}
fun function(
func: KFunction4,
variantType: VariantType,
p0Type: Pair,
p1Type: Pair,
p2Type: Pair,
p0: KtFunctionArgument,
p1: KtFunctionArgument,
p2: KtFunctionArgument,
returnType: KtFunctionArgument,
rpcModeId: Int = 0
) {
appendFunction(
KtFunction3(
KtFunctionInfo(
func.name.camelToSnakeCase(),
listOf(
p0.toKtPropertyInfo(),
p1.toKtPropertyInfo(),
p2.toKtPropertyInfo(),
),
returnType.toKtPropertyInfo(),
rpcModeId
),
func,
variantType,
p0Type,
p1Type,
p2Type
)
)
}
fun function(
func: KFunction4,
variantType: VariantType,
p0Type: Pair,
p1Type: Pair,
p2Type: Pair,
args: Array Unit>,
returns: KtPropertyInfoBuilderDsl.() -> Unit,
rpcModeId: Int = 0
) {
val (arguments, returnType) = argumentsAndReturnType(returns, *args)
require(args.size == 3) {
"Function ${func.name.camelToSnakeCase()} should have 3 arguments, found ${args.size}"
}
appendFunction(
KtFunction3(
KtFunctionInfo(func.name.camelToSnakeCase(), arguments, returnType, rpcModeId),
func,
variantType,
p0Type,
p1Type,
p2Type
)
)
}
fun function(
func: KFunction5,
variantType: VariantType,
p0Type: Pair,
p1Type: Pair,
p2Type: Pair,
p3Type: Pair,
p0: KtFunctionArgument,
p1: KtFunctionArgument,
p2: KtFunctionArgument,
p3: KtFunctionArgument,
returnType: KtFunctionArgument,
rpcModeId: Int = 0
) {
appendFunction(
KtFunction4(
KtFunctionInfo(
func.name.camelToSnakeCase(),
listOf(
p0.toKtPropertyInfo(),
p1.toKtPropertyInfo(),
p2.toKtPropertyInfo(),
p3.toKtPropertyInfo(),
),
returnType.toKtPropertyInfo(),
rpcModeId
),
func,
variantType,
p0Type,
p1Type,
p2Type,
p3Type
)
)
}
fun function(
func: KFunction5,
variantType: VariantType,
p0Type: Pair,
p1Type: Pair,
p2Type: Pair,
p3Type: Pair,
args: Array Unit>,
returns: KtPropertyInfoBuilderDsl.() -> Unit,
rpcModeId: Int = 0
) {
val (arguments, returnType) = argumentsAndReturnType(returns, *args)
require(args.size == 4) {
"Function ${func.name.camelToSnakeCase()} should have 4 arguments, found ${args.size}"
}
appendFunction(
KtFunction4(
KtFunctionInfo(func.name.camelToSnakeCase(), arguments, returnType, rpcModeId),
func,
variantType,
p0Type,
p1Type,
p2Type,
p3Type
)
)
}
fun function(
func: KFunction6,
variantType: VariantType,
p0Type: Pair,
p1Type: Pair,
p2Type: Pair,
p3Type: Pair,
p4Type: Pair,
p0: KtFunctionArgument,
p1: KtFunctionArgument,
p2: KtFunctionArgument,
p3: KtFunctionArgument,
p4: KtFunctionArgument,
returnType: KtFunctionArgument,
rpcModeId: Int = 0
) {
appendFunction(
KtFunction5(
KtFunctionInfo(
func.name.camelToSnakeCase(),
listOf(
p0.toKtPropertyInfo(),
p1.toKtPropertyInfo(),
p2.toKtPropertyInfo(),
p3.toKtPropertyInfo(),
p4.toKtPropertyInfo()
),
returnType.toKtPropertyInfo(),
rpcModeId
),
func,
variantType,
p0Type,
p1Type,
p2Type,
p3Type,
p4Type
)
)
}
fun function(
func: KFunction6,
variantType: VariantType,
p0Type: Pair,
p1Type: Pair,
p2Type: Pair,
p3Type: Pair,
p4Type: Pair,
args: Array Unit>,
returns: KtPropertyInfoBuilderDsl.() -> Unit,
rpcModeId: Int = 0
) {
val (arguments, returnType) = argumentsAndReturnType(returns, *args)
require(args.size == 5) {
"Function ${func.name.camelToSnakeCase()} should have 5 arguments, found ${args.size}"
}
appendFunction(
KtFunction5(
KtFunctionInfo(func.name.camelToSnakeCase(), arguments, returnType, rpcModeId),
func,
variantType,
p0Type,
p1Type,
p2Type,
p3Type,
p4Type
)
)
}
fun signal(kProperty: KProperty) {
appendSignal(
KtSignalInfo(kProperty.name.removePrefix("signal").camelToSnakeCase(), listOf())
)
}
fun signal(
kProperty: KProperty,
p0: KtFunctionArgument
) {
appendSignal(
KtSignalInfo(
kProperty.name.removePrefix("signal").camelToSnakeCase(),
listOf(
p0.toKtPropertyInfo()
)
)
)
}
fun signal(
kProperty: KProperty,
p0: KtFunctionArgument,
p1: KtFunctionArgument
) {
appendSignal(
KtSignalInfo(
kProperty.name.removePrefix("signal").camelToSnakeCase(),
listOf(
p0.toKtPropertyInfo(),
p1.toKtPropertyInfo()
)
)
)
}
fun signal(
kProperty: KProperty,
p0: KtFunctionArgument,
p1: KtFunctionArgument,
p2: KtFunctionArgument
) {
appendSignal(
KtSignalInfo(
kProperty.name.removePrefix("signal").camelToSnakeCase(),
listOf(
p0.toKtPropertyInfo(),
p1.toKtPropertyInfo(),
p2.toKtPropertyInfo()
)
)
)
}
fun signal(
kProperty: KProperty,
p0: KtFunctionArgument,
p1: KtFunctionArgument,
p2: KtFunctionArgument,
p3: KtFunctionArgument
) {
appendSignal(
KtSignalInfo(
kProperty.name.removePrefix("signal").camelToSnakeCase(),
listOf(
p0.toKtPropertyInfo(),
p1.toKtPropertyInfo(),
p2.toKtPropertyInfo(),
p3.toKtPropertyInfo()
)
)
)
}
fun signal(
kProperty: KProperty,
p0: KtFunctionArgument,
p1: KtFunctionArgument,
p2: KtFunctionArgument,
p3: KtFunctionArgument,
p4: KtFunctionArgument
) {
appendSignal(
KtSignalInfo(
kProperty.name.removePrefix("signal").camelToSnakeCase(),
listOf(
p0.toKtPropertyInfo(),
p1.toKtPropertyInfo(),
p2.toKtPropertyInfo(),
p3.toKtPropertyInfo(),
p4.toKtPropertyInfo()
)
)
)
}
fun signal(kProperty: KProperty, args: Array Unit> = arrayOf()) {
appendSignal(
KtSignalInfo(kProperty.name.removePrefix("signal").camelToSnakeCase(), args.applyArgumentsDsl())
)
}
private fun argumentsAndReturnType(
returns: KtPropertyInfoBuilderDsl.() -> Unit,
vararg args: KtPropertyInfoBuilderDsl.() -> Unit
): Pair, KtPropertyInfo> {
val returnBuilder = KtPropertyInfoBuilderDsl()
returnBuilder.returns()
val returnInfo = returnBuilder.build()
return args.applyArgumentsDsl() to returnInfo
}
private fun appendFunction(function: KtFunction) {
require(!functions.containsKey(function.functionInfo.name)) {
"A method with ${function.functionInfo.name} already exists."
}
functions[function.functionInfo.name] = function
}
@PublishedApi
internal fun appendSignal(signalInfo: KtSignalInfo) {
require(!signals.containsKey(signalInfo.name)) {
"A signal with ${signalInfo.name} already exists."
}
signals[signalInfo.name] = signalInfo
}
internal fun build(): KtClass {
check(constructors.isNotEmpty()) { "Please provide at least one constructor." }
// CONSTRUCTOR_MAX_ARGS + 1 because we have no arg constructor.
val constructorArray = arrayOfNulls>(CONSTRUCTOR_MAX_ARGS + 1)
constructors.forEach {
constructorArray[it.key] = it.value
}
return KtClass(name, registeredName, superClass, constructorArray, properties, functions, signals, baseGodotClass)
}
@PublishedApi
internal fun Array Unit>.applyArgumentsDsl(): List {
val argumentsCheckList = mutableSetOf()
return map {
val builder = KtPropertyInfoBuilderDsl()
builder.it()
val propertyInfo = builder.build()
require(!argumentsCheckList.contains(propertyInfo.name)) {
"Cannot have two arguments with name ${propertyInfo.name}"
}
require(propertyInfo.name.isNotEmpty()) { "Function parameters should have names." }
argumentsCheckList.add(propertyInfo.name)
propertyInfo
}
}
}
class ClassRegistry {
val classes = mutableListOf>()
fun registerClass(
resPath: String,
superClass: String,
kClass: KClass,
isTool: Boolean = false,
baseGodotClass: String,
registeredName: String = resPath.replace('.', '_'),
cb: ClassBuilderDsl.() -> Unit
) {
val builder = ClassBuilderDsl(resPath, registeredName, superClass, baseGodotClass)
builder.cb()
TypeManager.registerUserType(resPath, kClass)
registerClass(builder.build())
}
private fun registerClass(cls: KtClass) {
classes.add(cls)
}
}
interface ClassRegistrar {
fun register(registry: ClassRegistry)
}