From 3b1428597b0237ea3995edbc2af5e0927029838c Mon Sep 17 00:00:00 2001 From: Alexander Gorshenev Date: Fri, 15 Sep 2017 13:50:55 +0300 Subject: [PATCH] A little more stubs for html demo. Tweaked arena management a little. --- samples/html5Canvas/build.sh | 9 +- samples/html5Canvas/src/canvas/js/canvas.js | 65 ++++++++--- .../html5Canvas/src/canvas/kotlin/canvas.kt | 105 +++++++++++++++--- .../html5Canvas/src/jsinterop/js/jsinterop.js | 67 ++++++++--- .../src/jsinterop/kotlin/jsinterop.kt | 64 ++++++++--- samples/html5Canvas/src/main/kotlin/main.kt | 40 +++---- 6 files changed, 268 insertions(+), 82 deletions(-) diff --git a/samples/html5Canvas/build.sh b/samples/html5Canvas/build.sh index 384d0304d57..7460bf83da7 100755 --- a/samples/html5Canvas/build.sh +++ b/samples/html5Canvas/build.sh @@ -17,18 +17,19 @@ var=COMPILER_ARGS_${TARGET} COMPILER_ARGS=${!var} # add -opt for an optimized build. mkdir -p $DIR/build/bin +mkdir -p $DIR/build/klib konanc $DIR/src/jsinterop/kotlin \ -includeBinary $DIR/src/jsinterop/js/jsinterop.js \ - -p library -o $DIR/build/jsinterop -target wasm32 || exit 1 + -p library -o $DIR/build/klib/jsinterop -target wasm32 || exit 1 konanc $DIR/src/canvas/kotlin \ -includeBinary $DIR/src/canvas/js/canvas.js \ - -l $DIR/build/jsinterop \ - -p library -o $DIR/build/canvas -target wasm32 || exit 1 + -l $DIR/build/klib/jsinterop \ + -p library -o $DIR/build/klib/canvas -target wasm32 || exit 1 konanc $DIR/src/main/kotlin \ - -l $DIR/build/jsinterop -l $DIR/build/canvas \ + -r $DIR/build -l jsinterop -l canvas \ -o $DIR/build/bin/html5Canvas -target wasm32 || exit 1 echo "Artifact path is $DIR/build/bin/html5Canvas.wasm" diff --git a/samples/html5Canvas/src/canvas/js/canvas.js b/samples/html5Canvas/src/canvas/js/canvas.js index 22758a5d37d..cc9daa99701 100644 --- a/samples/html5Canvas/src/canvas/js/canvas.js +++ b/samples/html5Canvas/src/canvas/js/canvas.js @@ -2,32 +2,67 @@ konan.libraries.push ({ // This one will be auto generated. knjs_addDocumentToArena: function(arena) { // Should be resolved by jsinterop. - return konan_dependencies.env.Konan_js_addObjectToArena(arena, document); + return toArena(arena, document); }, - knjs_setInterval: function (func, interval) { - setInterval(konan_dependencies.env.Konan_js_wrapLambda(func), interval); + knjs_setInterval: function (arena, func, interval) { + setInterval(konan_dependencies.env.Konan_js_wrapLambda(arena, func), interval); }, - knjs_getElementById: function (arena, obj, id, idLength) { + knjs_getElementById: function (arena, obj, id, idLength, resultArena) { var name = toUTF16String(id, idLength); - var result = konan_dependencies.env.arenas[arena][obj].getElementById(name); - return konan_dependencies.env.Konan_js_addObjectToArena(arena, result); + var result = kotlinObject(arena, obj).getElementById(name); + return toArena(resultArena, result); }, - knjs_getContext: function (arena, obj, context, contextLength) { + knjs_getContext: function (arena, obj, context, contextLength, resultArena) { var name = toUTF16String(context, contextLength); - var result = konan_dependencies.env.arenas[arena][obj].getContext(name); - return konan_dependencies.env.Konan_js_addObjectToArena(arena, result); + var result = kotlinObject(arena, obj).getContext(name); + return toArena(resultArena, result); }, - knjs_getBoundingClientRect: function (arena, obj) { - var result = konan_dependencies.env.arenas[arena][obj].getBoundingClientRect(); - return konan_dependencies.env.Konan_js_addObjectToArena(arena, result); + knjs_getBoundingClientRect: function (arena, obj, resultArena) { + var result = kotlinObject(arena, obj).getBoundingClientRect(); + return toArena(resultArena, result); }, knjs_moveTo: function(arena, obj, x, y) { - konan_dependencies.env.arenas[arena][obj].moveTo(x, y); + kotlinObject(arena, obj).moveTo(x, y); }, knjs_lineTo: function(arena, obj, x, y) { - konan_dependencies.env.arenas[arena][obj].lineTo(x, y); + kotlinObject(arena, obj).lineTo(x, y); + }, + knjs_fillRect: function(arena, obj, x1, y1, width, height) { + kotlinObject(arena, obj).fillRect(x1, y1, width, height); + }, + knjs_fill: function(arena, obj) { + kotlinObject(arena, obj).fill(); + }, + knjs_closePath: function(arena, obj) { + kotlinObject(arena, obj).closePath(); + }, + knjs_setLineWidth: function(arena, obj, value) { + kotlinObject(arena, obj).lineWidth = value; + }, + knjs_setFillStyle: function(arena, obj, value_ptr, value_length) { + var value = toUTF16String(value_ptr, value_length); + kotlinObject(arena, obj).fillStyle = value; + }, + knjs_beginPath: function(arena, obj) { + kotlinObject(arena, obj).beginPath(); }, knjs_stroke: function(arena, obj) { - konan_dependencies.env.arenas[arena][obj].stroke(); + kotlinObject(arena, obj).stroke(); }, + knjs_fetch: function(arena, urlPtr, urlLength, resultArena) { + var url = toUTF16String(urlPtr, urlLength); + var result = fetch(url); + return toArena(resultArena, result); + }, + knjs_then: function(arena, obj, func, resultArena) { + var value = konan_dependencies.env.Konan_js_wrapLambda(arena, func); + var result = kotlinObject(arena, obj).then(value); + var index = toArena(resultArena, result); + return index; + }, + knjs_json: function(arena, obj, resultArena) { + var object = kotlinObject(arena, obj); + result = object.json(); + return toArena(resultArena, result); + } }); diff --git a/samples/html5Canvas/src/canvas/kotlin/canvas.kt b/samples/html5Canvas/src/canvas/kotlin/canvas.kt index cb355282f3c..8d9ee020bf9 100644 --- a/samples/html5Canvas/src/canvas/kotlin/canvas.kt +++ b/samples/html5Canvas/src/canvas/kotlin/canvas.kt @@ -8,13 +8,16 @@ import kotlinx.wasm.jsinterop.* external public fun addDocumentToArena(arena: Arena): Int @SymbolName("knjs_getElementById") -external public fun knjs_getElementById(arena: Int, obj: Int, idPtr: Int, idLen: Int): Int; +external public fun knjs_getElementById(arena: Int, obj: Int, idPtr: Int, idLen: Int, resultArena: Int): Int; @SymbolName("knjs_getContext") -external public fun knjs_getContext(arena: Int, obj: Int, idPtr: Int, idLen: Int): Int; +external public fun knjs_getContext(arena: Int, obj: Int, idPtr: Int, idLen: Int, resultArena: Int): Int; @SymbolName("knjs_getBoundingClientRect") -external public fun knjs_getBoundingClientRect(arena: Int, obj: Int): Int; +external public fun knjs_getBoundingClientRect(arena: Int, obj: Int, resultArena: Int): Int; + +@SymbolName("knjs_beginPath") +external public fun knjs_beginPath(arena: Int, obj: Int); @SymbolName("knjs_stroke") external public fun knjs_stroke(arena: Int, obj: Int); @@ -25,13 +28,37 @@ external public fun knjs_moveTo(arena: Int, obj: Int, x: Int, y: Int): Int; @SymbolName("knjs_lineTo") external public fun knjs_lineTo(arena: Int, obj: Int, x: Int, y: Int): Int; +@SymbolName("knjs_fillRect") +external public fun knjs_fillRect(arena: Int, obj: Int, x1: Int, y1: Int, width: Int, height: Int) + +@SymbolName("knjs_fill") +external public fun knjs_fill(arena: Int, obj: Int) + +@SymbolName("knjs_closePath") +external public fun knjs_closePath(arena: Int, obj: Int) + +@SymbolName("knjs_setLineWidth") +external public fun knjs_setLineWidth(thisArena: Int, thisIndex: Int, value: Int) +@SymbolName("knjs_setFillStyle") +external public fun knjs_setFillStyle(thisArena: Int, thisIndex: Int, valuePtr: Int, valueLength: Int) + @SymbolName("knjs_setInterval") -external public fun knjs_setInterval(functionIndex: Int, interval: Int); +external public fun knjs_setInterval(arenaIndex: Int, functionIndex: Int, interval: Int); + +@SymbolName("knjs_fetch") +external public fun knjs_fetch(arena: Int, urlPtr: Int, urlLen: Int, resultArena: Int): Int; + +@SymbolName("knjs_then") +external public fun knjs_then(thisArena: Int, thisIndex: Int, functionIndex: Int, resultArena: Int): Int + +@SymbolName("knjs_json") +external public fun knjs_json(thisArena: Int, thisIndex: Int, resultArena: Int): Int open class Document(arena: Int, index: Int): JsValue(arena, index) { + constructor(value: JsValue): this(value.arena, value.index) fun getElementById(id: String): JsValue { - val resultIndex = knjs_getElementById(this.arena, this.index, stringPointer(id), stringLengthBytes(id)) - return JsValue(this.arena, resultIndex) + val resultIndex = knjs_getElementById(this.arena, this.index, stringPointer(id), stringLengthBytes(id), ArenaManager.currentArena) + return JsValue(ArenaManager.currentArena, resultIndex) } } @@ -40,11 +67,11 @@ open class DOMRect(arena: Int, index: Int): JsValue(arena, index) { open class Canvas(arena: Int, index: Int): JsValue(arena, index) { fun getContext(context: String): Context { - val resultIndex = knjs_getContext(this.arena, this.index, stringPointer(context), stringLengthBytes(context)) - return Context(this.arena, resultIndex) + val resultIndex = knjs_getContext(this.arena, this.index, stringPointer(context), stringLengthBytes(context), ArenaManager.currentArena) + return Context(ArenaManager.currentArena, resultIndex) } fun getBoundingClientRect(): DOMRect { - return DOMRect(this.arena, knjs_getBoundingClientRect(this.arena, this.index)) + return DOMRect(ArenaManager.currentArena, knjs_getBoundingClientRect(this.arena, this.index, ArenaManager.currentArena)) } } @@ -61,20 +88,62 @@ open class Context(arena: Int, index: Int): JsValue(arena, index) { fun moveTo(x: Int, y: Int) { knjs_moveTo(this.arena, this.index, x, y) } + fun beginPath() { + knjs_beginPath(this.arena, this.index); + } fun stroke() { knjs_stroke(this.arena, this.index); } -} -fun setInterval(lambda: (ArrayList)->Unit, interval: Int) { - knjs_setInterval(wrapFunction(lambda), interval); + fun fillRect(x: Int, y: Int, width: Int, height: Int) { + knjs_fillRect(this.arena, this.index, x, y, width, height) + } + fun fill() { + knjs_fill(this.arena, this.index) + } + fun closePath() { + knjs_closePath(this.arena, this.index) + } + var lineWidth: Int + get() = TODO("implement me") + set(value: Int) { + knjs_setLineWidth(this.arena, this.index, value) + } + var fillStyle: String + get() = TODO("implement me") + set(value: String) { + knjs_setFillStyle(this.arena, this.index, stringPointer(value), stringLengthBytes(value)) + } } -// TODO: The arguments are flipped to have lambda within the last position. -fun setInterval(interval: Int, lambda: (ArrayList)->Unit) - = setInterval(lambda, interval) -class html5 { - val arena = allocateArena() - val document = Document(arena, addDocumentToArena(arena)) +class Response(arena: Int, index: Int): JsValue(arena, index) { + constructor(value: JsValue): this(value.arena, value.index) + fun json(): JsValue { + return JsValue(ArenaManager.currentArena, knjs_json(this.arena, this.index, ArenaManager.currentArena)) + } } +class Html5() { + val document = Document(ArenaManager.currentArena, addDocumentToArena(ArenaManager.currentArena)) + + fun fetch(url: String): Promise { + return Promise (ArenaManager.currentArena, knjs_fetch(ArenaManager.currentArena, stringPointer(url), stringLengthBytes(url), ArenaManager.currentArena)) + } + + fun setInterval(lambda: KtFunction, interval: Int) { + knjs_setInterval(ArenaManager.globalArena, wrapFunction(lambda), interval); + } + + // TODO: The arguments are flipped to have lambda within the last position. + fun setInterval(interval: Int, lambda: KtFunction) + = setInterval(lambda, interval) + +} + +open class Promise (arena: Int, index: Int): JsValue(arena, index) { + fun then(lambda: KtFunction): Promise { + return Promise(ArenaManager.currentArena, knjs_then(arena, index, wrapFunction(lambda), ArenaManager.currentArena)) + } +} + + diff --git a/samples/html5Canvas/src/jsinterop/js/jsinterop.js b/samples/html5Canvas/src/jsinterop/js/jsinterop.js index 221b12f4efb..81a72e3c8e6 100644 --- a/samples/html5Canvas/src/jsinterop/js/jsinterop.js +++ b/samples/html5Canvas/src/jsinterop/js/jsinterop.js @@ -1,41 +1,82 @@ konan.libraries.push ({ - arenas: [], + arenas: new Map(), + nextArena: 0, Konan_js_allocateArena: function (array) { - konan_dependencies.env.arenas.push(array || []); - return konan_dependencies.env.arenas.length - 1; + var index = konan_dependencies.env.nextArena++; + konan_dependencies.env.arenas.set(index, array || []); + return index; }, Konan_js_freeArena: function(arenaIndex) { - var arena = konan_dependencies.env.arenas[arenaIndex]; + var arena = konan_dependencies.env.arenas.get(arenaIndex); arena.forEach(function(element, index) { arena[index] = null; }); - konan_dependencies.env.arenas[arenaIndex] = null; + konan_dependencies.env.arenas.delete(arenaIndex); + }, + Konan_js_pushIntToArena: function (arenaIndex, value) { + var arena = konan_dependencies.env.arenas.get(arenaIndex); + arena.push(value); + return arena.length - 1; }, Konan_js_addObjectToArena: function (arenaIndex, object) { - var arena = konan_dependencies.env.arenas[arenaIndex]; + var arena = konan_dependencies.env.arenas.get(arenaIndex); arena.push(object); return arena.length - 1; }, - Konan_js_wrapLambda: function (index) { + Konan_js_wrapLambda: function (functionArenaIndex, index) { return (function () { + var functionArena = konan_dependencies.env.arenas.get(functionArenaIndex); + // convert Arguments to an array // to be provided by launcher.js - var arenaIndex = konan_dependencies.env.Konan_js_allocateArena(Array.prototype.slice.call(arguments)); - // To be provided by Kotlin runtime. - instance.exports.Konan_js_runLambda(index, arenaIndex, arguments.length); - konan_dependencies.env.Konan_js_freeArena(arenaIndex); + var argumentArenaIndex = konan_dependencies.env.Konan_js_allocateArena(Array.prototype.slice.call(arguments)); + + var resultIndex = instance.exports.Konan_js_runLambda(index, argumentArenaIndex, arguments.length); + var result = kotlinObject(argumentArenaIndex, resultIndex); + konan_dependencies.env.Konan_js_freeArena(argumentArenaIndex); + + return result; }); }, Konan_js_getInt: function(arenaIndex, objIndex, propertyNamePtr, propertyNameLength) { // TODO: The toUTF16String() is to be resolved by launcher.js runtime. var property = toUTF16String(propertyNamePtr, propertyNameLength); - var value = konan_dependencies.env.arenas[arenaIndex][objIndex][property]; + var value = kotlinObject(arenaIndex, objIndex)[property]; return value; }, + Konan_js_getProperty: function(arenaIndex, objIndex, propertyNamePtr, propertyNameLength) { + // TODO: The toUTF16String() is to be resolved by launcher.js runtime. + var property = toUTF16String(propertyNamePtr, propertyNameLength); + var arena = konan_dependencies.env.arenas.get(arenaIndex); + var value = arena[objIndex][property]; + arena.push(value); + return arena.length - 1; + }, Konan_js_setFunction: function (arena, obj, propertyName, propertyNameLength, func) { var name = toUTF16String(propertyName, propertyNameLength); - konan_dependencies.env.arenas[arena][obj][name] = konan_dependencies.env.Konan_js_wrapLambda(func); + kotlinObject(arena, obj)[name] = konan_dependencies.env.Konan_js_wrapLambda(arena, func); + }, + + Konan_js_setString: function (arena, obj, propertyName, propertyNameLength, stringPtr, stringLength) { + var name = toUTF16String(propertyName, propertyNameLength); + var string = toUTF16String(stringPtr, stringLength); + kotlinObject(arena, obj)[name] = string; }, }); + +// TODO: This is just a shorthand notation. +function kotlinObject(arenaIndex, objectIndex) { + var arena = konan_dependencies.env.arenas.get(arenaIndex); + if (typeof arena == "undefined") { + console.log("No arena index " + arenaIndex + "for object" + objectIndex); + console.trace() + } + return arena[objectIndex] +} + +function toArena(arenaIndex, object) { + return konan_dependencies.env.Konan_js_addObjectToArena(arenaIndex, object); +} + diff --git a/samples/html5Canvas/src/jsinterop/kotlin/jsinterop.kt b/samples/html5Canvas/src/jsinterop/kotlin/jsinterop.kt index 03be07b7a05..86e7296d1f0 100644 --- a/samples/html5Canvas/src/jsinterop/kotlin/jsinterop.kt +++ b/samples/html5Canvas/src/jsinterop/kotlin/jsinterop.kt @@ -13,49 +13,87 @@ external public fun allocateArena(): Arena @SymbolName("Konan_js_freeArena") external public fun freeArena(arena: Arena) +@SymbolName("Konan_js_pushIntToArena") +external public fun pushIntToArena(arena: Arena, value: Int) + @SymbolName("Kotlin_String_utf16pointer") external public fun stringPointer(message: String): Pointer @SymbolName("Kotlin_String_utf16length") external public fun stringLengthBytes(message: String): Int -typealias KtFunction = (ArrayList)->Unit +typealias KtFunction = ((ArrayList)->R) -fun wrapFunction(func: KtFunction): Int { +fun wrapFunction(func: KtFunction): Int { val ptr: Long = StableObjPtr.create(func).value.toLong() return ptr.toInt() // TODO: LP64 unsafe. } @ExportForCppRuntime("Konan_js_runLambda") -fun runLambda(userData: Int, arena: Arena, arenaSize: Int) { +fun runLambda(pointer: Int, argumentsArena: Arena, argumentsArenaSize: Int): Int { val arguments = arrayListOf() - for (i in 0..arenaSize-1) { - arguments.add(JsValue(arena, i)); + for (i in 0 until argumentsArenaSize) { + arguments.add(JsValue(argumentsArena, i)); } + val previousArena = ArenaManager.currentArena + ArenaManager.currentArena = argumentsArena // TODO: LP64 unsafe: wasm32 passes Int, not Long. - val func = StableObjPtr.fromValue(userData.toLong().toCPointer()!!).get() as KtFunction - func(arguments); + val func = StableObjPtr.fromValue(pointer.toLong().toCPointer()!!).get() as KtFunction + val result = func(arguments) - + ArenaManager.currentArena = previousArena + return result.index } open class JsValue(val arena: Arena, val index: Object) { fun getInt(property: String): Int { - return getInt(arena, index, stringPointer(property), stringLengthBytes(property)) + return getInt(ArenaManager.currentArena, index, stringPointer(property), stringLengthBytes(property)) } + fun getProperty(property: String): JsValue { + return JsValue(ArenaManager.currentArena, Konan_js_getProperty(ArenaManager.currentArena, index, stringPointer(property), stringLengthBytes(property))) + } +} + +open class JsArray(arena: Arena, index: Object): JsValue(arena, index) { + constructor(jsValue: JsValue): this(jsValue.arena, jsValue.index) + operator fun get(index: Int): JsValue { + // TODO: we could pass an integer index to index arrays. + return getProperty(index.toString()) + } + val size: Int + get() = this.getInt("length") } @SymbolName("Konan_js_getInt") external public fun getInt(arena: Arena, obj: Object, propertyPtr: Pointer, propertyLen: Int): Int; +@SymbolName("Konan_js_getProperty") +external public fun Konan_js_getProperty(arena: Arena, obj: Object, propertyPtr: Pointer, propertyLen: Int): Int; + @SymbolName("Konan_js_setFunction") external public fun setFunction(arena: Arena, obj: Object, propertyName: Pointer, propertyLength: Int , function: Int) -fun setter(obj: JsValue, property: String, lambda: (arguments: ArrayList) -> Unit) { - val index = wrapFunction(lambda); - setFunction(obj.arena, obj.index, stringPointer(property), stringLengthBytes(property), index) +@SymbolName("Konan_js_setString") +external public fun setString(arena: Arena, obj: Object, propertyName: Pointer, propertyLength: Int, stringPtr: Pointer, stringLength: Int ) + +fun setter(obj: JsValue, property: String, string: String) { + setString(obj.arena, obj.index, stringPointer(property), stringLengthBytes(property), stringPointer(string), stringLengthBytes(string)) } -fun JsValue.setter(property: String, lambda: (ArrayList) -> Unit) { +fun setter(obj: JsValue, property: String, lambda: KtFunction) { + val pointer = wrapFunction(lambda); + setFunction(obj.arena, obj.index, stringPointer(property), stringLengthBytes(property), pointer) +} + +fun JsValue.setter(property: String, lambda: KtFunction) { setter(this, property, lambda) } + +fun JsValue.setter(property: String, string: String) { + setter(this, property, string) +} + +object ArenaManager { + val globalArena = allocateArena() + var currentArena = globalArena +} diff --git a/samples/html5Canvas/src/main/kotlin/main.kt b/samples/html5Canvas/src/main/kotlin/main.kt index f232ec8aa69..c2d67d1d0a5 100644 --- a/samples/html5Canvas/src/main/kotlin/main.kt +++ b/samples/html5Canvas/src/main/kotlin/main.kt @@ -3,44 +3,46 @@ import kotlinx.wasm.jsinterop.* import konan.internal.ExportForCppRuntime fun main(args: Array) { - val html5 = html5() + + val html5 = Html5() val document = html5.document - val canvas = document.getElementById("myCanvas").asCanvas; - val ctx = canvas.getContext("2d"); - val rect = canvas.getBoundingClientRect(); + val canvas = document.getElementById("myCanvas").asCanvas + val ctx = canvas.getContext("2d") + val rect = canvas.getBoundingClientRect() val rectLeft = rect.getInt("left") val rectTop = rect.getInt("top") - var mouseX: Int = 0; - var mouseY: Int = 0; - var draw: Boolean = false; + var mouseX: Int = 0 + var mouseY: Int = 0 + var draw: Boolean = false document.setter("onmousemove") { args: ArrayList -> - val event = args[0]; + val event = args[0] mouseX = event.getInt("clientX") - rectLeft mouseY = event.getInt("clientY") - rectTop - if (mouseX < 0) mouseX = 0; - if (mouseX > 639) mouseX = 639; - if (mouseY < 0) mouseY = 0; - if (mouseY > 479) mouseY = 479; + if (mouseX < 0) mouseX = 0 + if (mouseX > 639) mouseX = 639 + if (mouseY < 0) mouseY = 0 + if (mouseY > 479) mouseY = 479 } document.setter("onmousedown") { - draw = true; + draw = true } document.setter("onmouseup") { - draw = false; + draw = false } - setInterval(10) { + html5.setInterval(10) { if (draw) { - ctx.lineTo(mouseX, mouseY); - ctx.stroke(); + ctx.setter("strokeStyle", "#222222") + ctx.lineTo(mouseX, mouseY) + ctx.stroke() } else { - ctx.moveTo(mouseX, mouseY); - ctx.stroke(); + ctx.moveTo(mouseX, mouseY) + ctx.stroke() } } }