Native: remove samples that are not used in tests
This commit is contained in:
committed by
Space
parent
d22cccdc4d
commit
71b8089f33
@@ -13,8 +13,6 @@ More Kotlin Multiplatform Mobile samples can be found here: https://kotlinlang.o
|
||||
The samples that are in this directory mostly illustrate the other use cases for Kotlin/Native:
|
||||
* `csvparser` - simple CSV file parser and analyzer
|
||||
* `echoServer` - TCP/IP echo server
|
||||
* `gitchurn` - program interoperating with `libgit2` for GIT repository analysis
|
||||
* `gtk` - GTK3 interoperability example
|
||||
* `html5Canvas` - WebAssembly example
|
||||
* `libcurl` - using of FTP/HTTP/HTTPS client library `libcurl`
|
||||
* `nonBlockingEchoServer` - multi-client TCP/IP echo server using co-routines
|
||||
@@ -22,7 +20,6 @@ The samples that are in this directory mostly illustrate the other use cases for
|
||||
* `opengl` - OpenGL/GLUT teapot example
|
||||
* `python_extension` - Python extension written in Kotlin/Native
|
||||
* `tensorflow` - simple client for TensorFlow Machine Intelligence library
|
||||
* `tetris` - Tetris game implementation (using SDL2 for rendering)
|
||||
* `uikit` - UIKit Objective-C interoperability example for iOS
|
||||
* `videoplayer` - SDL and FFMPEG-based video and audio player
|
||||
* `win32` - trivial Win32 GUI application
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# GIT frequency analyzer
|
||||
|
||||
This example shows how one could perform statistics on Git repository.
|
||||
|
||||
Install libgit2 development files.
|
||||
For Debian-like Linux - use `apt-get install libgit2-dev`.
|
||||
For Windows - `pacman -S mingw-w64-x86_64-libgit2` in MinGW64 console, if you do
|
||||
not have MSYS2-MinGW64 installed - install it first as described in http://www.msys2.org
|
||||
|
||||
To build use `../gradlew assemble`.
|
||||
|
||||
To run use `../gradlew runReleaseExecutableGitChurn` or execute the program directly:
|
||||
|
||||
./build/bin/gitChurn/main/release/executable/gitchurn.kexe ../../
|
||||
|
||||
It will print most frequently modified (by number of commits) files in repository.
|
||||
@@ -1,41 +0,0 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
val mingwPath = File(System.getenv("MINGW64_DIR") ?: "C:/msys64/mingw64")
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isMingwX64 = hostOs.startsWith("Windows")
|
||||
|
||||
// Create a target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("gitChurn")
|
||||
hostOs == "Linux" -> linuxX64("gitChurn")
|
||||
isMingwX64 -> mingwX64("gitChurn")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.gitchurn.main"
|
||||
if (isMingwX64) {
|
||||
linkerOpts("-L${mingwPath.resolve("lib")}")
|
||||
runTask?.environment("PATH" to mingwPath.resolve("bin"))
|
||||
}
|
||||
runTask?.args(rootProject.rootDir.resolve(".."))
|
||||
}
|
||||
}
|
||||
compilations["main"].cinterops {
|
||||
val libgit2 by creating {
|
||||
when (preset) {
|
||||
presets["macosX64"] -> includeDirs.headerFilterOnly("/opt/local/include", "/usr/local/include")
|
||||
presets["linuxX64"] -> includeDirs.headerFilterOnly("/usr/include")
|
||||
presets["mingwX64"] -> includeDirs.headerFilterOnly(mingwPath.resolve("include"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.gitchurn
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import libgit2.git_time_t
|
||||
import platform.posix.ctime
|
||||
import platform.posix.time_tVar
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
if (args.isEmpty())
|
||||
return help()
|
||||
|
||||
val workDir = args[0]
|
||||
|
||||
val limit = if (args.size > 1) {
|
||||
val limitRaw = args[1].toIntOrNull()
|
||||
if (limitRaw == null || limitRaw <= 0) {
|
||||
return help("Not a positive integer: $limitRaw")
|
||||
}
|
||||
limitRaw
|
||||
} else
|
||||
Int.MAX_VALUE
|
||||
|
||||
try {
|
||||
calculateChurn(workDir, limit)
|
||||
} catch (e: GitException) {
|
||||
help(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateChurn(workDir: String, limit: Int) {
|
||||
println("Opening…")
|
||||
val repository = git.repository(workDir)
|
||||
val map = mutableMapOf<String, Int>()
|
||||
var count = 0
|
||||
val commits = repository.commits()
|
||||
val limited = commits.take(limit)
|
||||
println("Calculating…")
|
||||
limited.forEach { commit ->
|
||||
if (count % 100 == 0)
|
||||
println("Commit #$count [${commit.time.format()}]: ${commit.summary}")
|
||||
|
||||
commit.parents.forEach { parent ->
|
||||
val diff = commit.tree.diff(parent.tree)
|
||||
diff.deltas().forEach { delta ->
|
||||
val path = delta.newPath
|
||||
val n = map[path] ?: 0
|
||||
map.put(path, n + 1)
|
||||
}
|
||||
diff.close()
|
||||
parent.close()
|
||||
}
|
||||
commit.close()
|
||||
count++
|
||||
}
|
||||
println("Report:")
|
||||
map.toList().sortedByDescending { it.second }.take(10).forEach {
|
||||
println("File: ${it.first}")
|
||||
println(" ${it.second}")
|
||||
println()
|
||||
}
|
||||
|
||||
repository.close()
|
||||
git.close()
|
||||
}
|
||||
|
||||
private fun git_time_t.format() = memScoped {
|
||||
val commitTime = alloc<time_tVar>()
|
||||
commitTime.value = this@format
|
||||
ctime(commitTime.ptr)!!.toKString().trim()
|
||||
}
|
||||
|
||||
|
||||
private fun printTree(commit: GitCommit) {
|
||||
commit.tree.entries().forEach { entry ->
|
||||
when (entry) {
|
||||
is GitTreeEntry.File -> println(" ${entry.name}")
|
||||
is GitTreeEntry.Folder -> println(" /${entry.name} (${entry.subtree.entries().size})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun help(errorMessage: String? = null) {
|
||||
errorMessage?.let {
|
||||
println("ERROR: $it")
|
||||
}
|
||||
println("./gitchurn.kexe <work dir> [<limit>]")
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.gitchurn
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import libgit2.*
|
||||
|
||||
class GitCommit(val repository: GitRepository, val commit: CPointer<git_commit>) {
|
||||
fun close() = git_commit_free(commit)
|
||||
|
||||
val summary: String get() = git_commit_summary(commit)!!.toKString()
|
||||
val time: git_time_t get() = git_commit_time(commit)
|
||||
|
||||
val tree: GitTree get() = memScoped {
|
||||
val treePtr = allocPointerTo<git_tree>()
|
||||
git_commit_tree(treePtr.ptr, commit).errorCheck()
|
||||
GitTree(repository, treePtr.value!!)
|
||||
}
|
||||
|
||||
val parents: List<GitCommit> get() = memScoped {
|
||||
val count = git_commit_parentcount(commit).toInt()
|
||||
val result = ArrayList<GitCommit>(count)
|
||||
for (index in 0..count - 1) {
|
||||
val commitPtr = allocPointerTo<git_commit>()
|
||||
git_commit_parent(commitPtr.ptr, commit, index.toUInt()).errorCheck()
|
||||
result.add(GitCommit(repository, commitPtr.value!!))
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.gitchurn
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import libgit2.*
|
||||
|
||||
class GitDiff(val repository: GitRepository, val handle: CPointer<git_diff>) {
|
||||
fun deltas(): List<GifDiffDelta> {
|
||||
val size = git_diff_num_deltas(handle).toInt()
|
||||
val results = ArrayList<GifDiffDelta>(size)
|
||||
for (index in 0..size - 1) {
|
||||
val delta = git_diff_get_delta(handle, index.convert())
|
||||
results.add(GifDiffDelta(this, delta!!))
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
fun close() {
|
||||
git_diff_free(handle)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GifDiffDelta(val diff: GitDiff, val handle: CPointer<git_diff_delta>) {
|
||||
|
||||
val status get() = handle.pointed.status
|
||||
val newPath get() = handle.pointed.new_file.path!!.toKString()
|
||||
val oldPath get() = handle.pointed.old_file.path!!.toKString()
|
||||
|
||||
fun status(): String {
|
||||
return when (status) {
|
||||
GIT_DELTA_ADDED -> "A"
|
||||
GIT_DELTA_DELETED -> "D"
|
||||
GIT_DELTA_MODIFIED -> "M"
|
||||
GIT_DELTA_RENAMED -> "R"
|
||||
GIT_DELTA_COPIED -> "C"
|
||||
else -> throw Exception("Unsupported delta status $status")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.gitchurn
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import libgit2.*
|
||||
|
||||
class GitRemote(val repository: GitRepository, val handle: CPointer<git_remote>) {
|
||||
fun close() = git_remote_free(handle)
|
||||
|
||||
val url: String get() = git_remote_url(handle)!!.toKString()
|
||||
val name: String = git_remote_name(handle)!!.toKString()
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.gitchurn
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import libgit2.*
|
||||
|
||||
class GitRepository(val location: String) {
|
||||
val arena = Arena()
|
||||
val handle: CPointer<git_repository> = memScoped {
|
||||
val loc = allocPointerTo<git_repository>()
|
||||
git_repository_open(loc.ptr, location).errorCheck()
|
||||
loc.value!!
|
||||
}
|
||||
|
||||
fun close() {
|
||||
git_repository_free(handle)
|
||||
arena.clear()
|
||||
}
|
||||
|
||||
fun remotes(): List<GitRemote> = memScoped {
|
||||
val remoteList = alloc<git_strarray>()
|
||||
git_remote_list(remoteList.ptr, handle).errorCheck()
|
||||
val size = remoteList.count.toInt()
|
||||
val list = ArrayList<GitRemote>(size)
|
||||
for (index in 0..size - 1) {
|
||||
val array = remoteList.strings!!
|
||||
val name = array[index]!!.toKString()
|
||||
val remotePtr = allocPointerTo<git_remote>()
|
||||
git_remote_lookup(remotePtr.ptr, handle, name).errorCheck()
|
||||
list.add(GitRemote(this@GitRepository, remotePtr.value!!))
|
||||
}
|
||||
list
|
||||
}
|
||||
|
||||
fun commits(): Sequence<GitCommit> = memScoped {
|
||||
val walkPtr = allocPointerTo<git_revwalk>()
|
||||
git_revwalk_new(walkPtr.ptr, handle).errorCheck()
|
||||
val walk = walkPtr.value
|
||||
git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL or GIT_SORT_TIME)
|
||||
git_revwalk_push_head(walk).errorCheck()
|
||||
generateSequence<GitCommit> {
|
||||
memScoped {
|
||||
val oid = alloc<git_oid>()
|
||||
val result = git_revwalk_next(oid.ptr, walk)
|
||||
|
||||
when (result) {
|
||||
0 -> {
|
||||
val commitPtr = allocPointerTo<git_commit>()
|
||||
git_commit_lookup(commitPtr.ptr, handle, oid.ptr).errorCheck()
|
||||
val commit = commitPtr.value!!
|
||||
GitCommit(this@GitRepository, commit)
|
||||
}
|
||||
GIT_ITEROVER -> null
|
||||
else -> throw Exception("Unexpected result code $result")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.gitchurn
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import libgit2.*
|
||||
|
||||
class GitTree(val repository: GitRepository, val handle: CPointer<git_tree>) {
|
||||
fun close() = git_tree_free(handle)
|
||||
|
||||
fun entries(): List<GitTreeEntry> = memScoped {
|
||||
val size = git_tree_entrycount(handle).toInt()
|
||||
val entries = ArrayList<GitTreeEntry>(size)
|
||||
for (index in 0..size - 1) {
|
||||
val treeEntry = git_tree_entry_byindex(handle, index.convert())!!
|
||||
val entryType = git_tree_entry_type(treeEntry)
|
||||
val entry = when (entryType) {
|
||||
GIT_OBJ_TREE -> memScoped {
|
||||
val id = git_tree_entry_id(treeEntry)
|
||||
val treePtr = allocPointerTo<git_tree>()
|
||||
git_tree_lookup(treePtr.ptr, repository.handle, id)
|
||||
GitTreeEntry.Folder(this@GitTree, treeEntry, treePtr.value!!)
|
||||
}
|
||||
GIT_OBJ_BLOB -> GitTreeEntry.File(this@GitTree, treeEntry)
|
||||
else -> throw Exception("Unsupported entry type $entryType")
|
||||
}
|
||||
entries.add(entry)
|
||||
}
|
||||
entries
|
||||
}
|
||||
|
||||
fun diff(other: GitTree): GitDiff = memScoped {
|
||||
val diffPtr = allocPointerTo<git_diff>()
|
||||
git_diff_tree_to_tree(diffPtr.ptr, repository.handle, handle, other.handle, null).errorCheck()
|
||||
GitDiff(repository, diffPtr.value!!)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class GitTreeEntry(val tree: GitTree, val handle: CPointer<git_tree_entry>) {
|
||||
val name: String get() = git_tree_entry_name(handle)!!.toKString()
|
||||
|
||||
class Folder(tree: GitTree, handle: CPointer<git_tree_entry>, val subtreeHandle: CPointer<git_tree>) : GitTreeEntry(tree, handle) {
|
||||
val subtree = GitTree(tree.repository, subtreeHandle)
|
||||
}
|
||||
|
||||
class File(tree: GitTree, handle: CPointer<git_tree_entry>) : GitTreeEntry(tree, handle)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.gitchurn
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import libgit2.*
|
||||
|
||||
object git {
|
||||
init {
|
||||
git_libgit2_init()
|
||||
}
|
||||
|
||||
fun close() {
|
||||
git_libgit2_shutdown()
|
||||
}
|
||||
|
||||
fun repository(location: String): GitRepository {
|
||||
return GitRepository(location)
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.errorCheck() {
|
||||
if (this == 0) return
|
||||
throw GitException()
|
||||
}
|
||||
|
||||
class GitException : Exception(run {
|
||||
val err = giterr_last()
|
||||
err!!.pointed.message!!.toKString()
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
headers = git2.h
|
||||
headerFilter = git2/** git2.h
|
||||
linkerOpts.osx = -L/opt/local/lib -L/usr/local/lib -lgit2
|
||||
linkerOpts.linux = -L/usr/lib64 -L/usr/lib/x86_64-linux-gnu -lgit2
|
||||
linkerOpts.mingw = -lgit2
|
||||
@@ -1,40 +0,0 @@
|
||||
# GTK application
|
||||
|
||||
This example shows how one may use _Kotlin/Native_ to build GUI
|
||||
applications with the GTK toolkit.
|
||||
|
||||
To build use `../gradlew assemble`.
|
||||
|
||||
Do not forget to install GTK3. See bellow.
|
||||
|
||||
To run on Mac also install XQuartz X server (https://www.xquartz.org/), and then `../gradlew runReleaseExecutableGtk` or execute the program directly:
|
||||
|
||||
./build/bin/gtk/main/release/executable/gtk.kexe
|
||||
|
||||
Dialog box with the button will be shown, and application will print message
|
||||
and terminate on button click.
|
||||
|
||||
|
||||
#### GTK3 Install
|
||||
|
||||
on Mac use
|
||||
|
||||
brew install gtk+3
|
||||
|
||||
or
|
||||
|
||||
port install gtk3
|
||||
|
||||
on Debian flavours of Linux
|
||||
|
||||
sudo apt-get install libgtk-3-dev
|
||||
|
||||
on Fedora
|
||||
|
||||
sudo dnf install gtk3-devel
|
||||
|
||||
on Windows in MinGW64 console, if you do
|
||||
not have MSYS2-MinGW64 installed - install it first as described in http://www.msys2.org
|
||||
|
||||
pacman -S mingw-w64-x86_64-gtk3
|
||||
pacman -S mingw-w64-x86_64-harfbuzz
|
||||
@@ -1,70 +0,0 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
val mingwPath = File(System.getenv("MINGW64_DIR") ?: "C:/msys64/mingw64")
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isMingwX64 = hostOs.startsWith("Windows")
|
||||
|
||||
// Create a target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("gtk")
|
||||
hostOs == "Linux" -> linuxX64("gtk")
|
||||
isMingwX64 -> mingwX64("gtk")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.gtk.main"
|
||||
if (isMingwX64) {
|
||||
linkerOpts("-L${mingwPath.resolve("lib")}")
|
||||
runTask?.environment("PATH" to mingwPath.resolve("bin"))
|
||||
}
|
||||
}
|
||||
}
|
||||
compilations["main"].cinterops {
|
||||
val gtk3 by creating {
|
||||
when (preset) {
|
||||
presets["macosX64"], presets["linuxX64"] -> {
|
||||
listOf("/opt/local/include", "/usr/include", "/usr/local/include").forEach {
|
||||
includeDirs(
|
||||
"$it/atk-1.0",
|
||||
"$it/gdk-pixbuf-2.0",
|
||||
"$it/cairo",
|
||||
"$it/harfbuzz",
|
||||
"$it/pango-1.0",
|
||||
"$it/gtk-3.0",
|
||||
"$it/glib-2.0"
|
||||
)
|
||||
}
|
||||
|
||||
includeDirs(
|
||||
"/opt/local/lib/glib-2.0/include",
|
||||
"/usr/lib/x86_64-linux-gnu/glib-2.0/include",
|
||||
"/usr/local/lib/glib-2.0/include"
|
||||
)
|
||||
}
|
||||
presets["mingwX64"] -> {
|
||||
listOf(
|
||||
"include/atk-1.0",
|
||||
"include/gdk-pixbuf-2.0",
|
||||
"include/cairo",
|
||||
"include/pango-1.0",
|
||||
"include/gtk-3.0",
|
||||
"include/glib-2.0",
|
||||
"include/harfbuzz",
|
||||
"lib/glib-2.0/include"
|
||||
).forEach {
|
||||
includeDirs(mingwPath.resolve(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
kotlin.native.jvmArgs=-Xmx8g
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.gtk
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import gtk3.*
|
||||
|
||||
// Note that all callback parameters must be primitive types or nullable C pointers.
|
||||
fun <F : CFunction<*>> g_signal_connect(obj: CPointer<*>, actionName: String,
|
||||
action: CPointer<F>, data: gpointer? = null, connect_flags: GConnectFlags = 0u) {
|
||||
g_signal_connect_data(obj.reinterpret(), actionName, action.reinterpret(),
|
||||
data = data, destroy_data = null, connect_flags = connect_flags)
|
||||
|
||||
}
|
||||
|
||||
fun activate(app: CPointer<GtkApplication>?, user_data: gpointer?) {
|
||||
val windowWidget = gtk_application_window_new(app)!!
|
||||
val window = windowWidget.reinterpret<GtkWindow>()
|
||||
gtk_window_set_title(window, "Window")
|
||||
gtk_window_set_default_size(window, 200, 200)
|
||||
|
||||
val button_box = gtk_button_box_new(
|
||||
GtkOrientation.GTK_ORIENTATION_HORIZONTAL)!!
|
||||
gtk_container_add(window.reinterpret(), button_box)
|
||||
|
||||
val button = gtk_button_new_with_label("Konan говорит: click me!")!!
|
||||
g_signal_connect(button, "clicked",
|
||||
staticCFunction { _: CPointer<GtkWidget>?, _: gpointer? -> println("Hi Kotlin")
|
||||
})
|
||||
g_signal_connect(button, "clicked",
|
||||
staticCFunction { widget: CPointer<GtkWidget>? ->
|
||||
gtk_widget_destroy(widget)
|
||||
},
|
||||
window, G_CONNECT_SWAPPED)
|
||||
gtk_container_add (button_box.reinterpret(), button)
|
||||
|
||||
gtk_widget_show_all(windowWidget)
|
||||
}
|
||||
|
||||
fun gtkMain(args: Array<String>): Int {
|
||||
val app = gtk_application_new("org.gtk.example", G_APPLICATION_FLAGS_NONE)!!
|
||||
g_signal_connect(app, "activate", staticCFunction(::activate))
|
||||
val status = memScoped {
|
||||
g_application_run(app.reinterpret(),
|
||||
args.size, args.map { it.cstr.ptr }.toCValues())
|
||||
}
|
||||
g_object_unref(app)
|
||||
return status
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
gtkMain(args)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
headers = gtk/gtk.h
|
||||
headerFilter = gtk/* gobject/* gio/*
|
||||
compilerOpts.osx = -I/usr/local/include/gtk-3.0 -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include \
|
||||
-I/usr/local/include/pango-1.0 -I/usr/local/include/cairo -I/usr/local/include -I/usr/local/include/gdk-pixbuf-2.0 \
|
||||
-I/usr/local/include/atk-1.0
|
||||
compilerOpts.linux = -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/local/lib/glib-2.0/include -I/usr/lib64/glib-2.0/include
|
||||
linkerOpts.osx = -L/opt/local/lib -L/usr/local/lib -lglib-2.0 -lgdk-3.0 -lgtk-3 -lgio-2.0 -lgobject-2.0
|
||||
linkerOpts.linux = -L/usr/lib64 -L/usr/lib/x86_64-linux-gnu -lglib-2.0 -lgdk-3 -lgtk-3 -lgio-2.0 -lgobject-2.0
|
||||
linkerOpts.mingw = -lglib-2.0 -lgdk-3 -lgtk-3 -lgio-2.0 -lgobject-2.0
|
||||
@@ -1,114 +0,0 @@
|
||||
# C to Kotlin/Native interoperability example
|
||||
|
||||
This example shows how to use Kotlin/Native programs from other execution environments, such as Python.
|
||||
Python has C native interface, which could be used to organize a bridge with the
|
||||
Kotlin/Native application. File `kotlin_bridge.c` contains translation code between Kotlin and Python
|
||||
lands. This demo works on Linux, Windows (64-bit) or macOS hosts.
|
||||
|
||||
To build and run the sample do the following:
|
||||
|
||||
* Install Python with development headers, i.e. on Linux
|
||||
```
|
||||
sudo apt install python-dev
|
||||
```
|
||||
|
||||
* Run `build.sh`, it will ask for superuser password to install Python extension.
|
||||
Use build.bat on Windows.
|
||||
* On macOS copy Kotlin binary to extension's directory and change install name with
|
||||
`install_name_tool` tool, i.e.
|
||||
```
|
||||
sudo cp ./build/libserver.dylib /Library/Python/2.7/site-packages/
|
||||
sudo install_name_tool /Library/Python/2.7/site-packages/kotlin_bridge.so \
|
||||
-change libserver.dylib @loader_path/libserver.dylib
|
||||
```
|
||||
* On Linux copy Kotlin binary in some place where libraries could be loaded from, i.e.
|
||||
```
|
||||
cp ./build/libserver.so /usr/local/lib/
|
||||
ldconfig
|
||||
```
|
||||
or modify dynamic loader search path with
|
||||
```
|
||||
export LD_LIBRARY_PATH=`pwd`
|
||||
```
|
||||
|
||||
* run Python code using Kotlin functionality with
|
||||
```
|
||||
python src/main/python/main.py
|
||||
```
|
||||
* it will show you result of using several Kotlin/Native APIs, accepting and returning both objects and
|
||||
primitive types
|
||||
|
||||
The example works as following. Kotlin/Native API is implemented in `Server.kt`, and we run Kotlin/Native compiler
|
||||
with `-produce dynamic` option. Compiler produces two artifacts: `server_api.h` which is C language API
|
||||
to all public functions and classes available in the application. `libserver.dylib` or `libserver.so` or `server.dll`
|
||||
shared object contains C bridge to all above APIs.
|
||||
|
||||
This C bridge looks like a C struct, reflecting all scopes in program, with operations available. For example,
|
||||
for class Server
|
||||
```c_cpp
|
||||
class Server(val prefix: String) {
|
||||
fun greet(session: Session) = "$prefix: Hello from Kotlin/Native in ${session}"
|
||||
fun concat(session: Session, a: String, b: String) = "$prefix: $a $b in ${session}"
|
||||
fun add(session: Session, a: Int, b: Int) = a + b + session.number
|
||||
}
|
||||
```
|
||||
following C API is produced
|
||||
```c_cpp
|
||||
typedef struct {
|
||||
server_KNativePtr pinned;
|
||||
} server_kref_demo_Session;
|
||||
typedef struct {
|
||||
server_KNativePtr pinned;
|
||||
} server_kref_demo_Server;
|
||||
|
||||
typedef struct {
|
||||
/* Service functions. */
|
||||
void (*DisposeStablePointer)(server_KNativePtr ptr);
|
||||
void (*DisposeString)(const char* string);
|
||||
server_KBoolean (*IsInstance)(server_KNativePtr ref, const server_KType* type);
|
||||
|
||||
/* User functions. */
|
||||
struct {
|
||||
struct {
|
||||
struct {
|
||||
server_KType* (*_type)(void);
|
||||
server_kref_demo_Session (*Session)(const char* name, server_KInt number);
|
||||
} Session;
|
||||
struct {
|
||||
server_KType* (*_type)(void);
|
||||
server_kref_demo_Server (*Server)(const char* prefix);
|
||||
const char* (*greet)(server_kref_demo_Server thiz, server_kref_demo_Session session);
|
||||
const char* (*concat)(server_kref_demo_Server thiz, server_kref_demo_Session session, const char* a, const char* b);
|
||||
server_KInt (*add)(server_kref_demo_Server thiz, server_kref_demo_Session session, server_KInt a, server_KInt b);
|
||||
} Server;
|
||||
} demo;
|
||||
} kotlin;
|
||||
} server_ExportedSymbols;
|
||||
extern server_ExportedSymbols* server_symbols(void);
|
||||
```
|
||||
|
||||
So every class instance is represented with a single element structure, encapsulating stable pointer to an instance.
|
||||
Once no longer needed, `DisposeStablePointer()` with that stable pointer shall be called, and if value is not stored
|
||||
somewhere else - it is disposed. For primitive types and `kotlin.String` smart bridges converting to C primitive types
|
||||
or to C strings (which has to be manually freed with `DisposeString()`) are implemented.
|
||||
|
||||
For example, running constructor of class Server taking a string will look like
|
||||
|
||||
server_kref_demo_Server server = server_symbols()->kotlin.demo.Server.Server("the server");
|
||||
|
||||
And disposing no longer needed instance will look like
|
||||
|
||||
server_symbols()->DisposeStablePointer(server.pinned);
|
||||
|
||||
To make code easier readable, macro definitions like
|
||||
|
||||
#define T_(name) server_kref_demo_ ## name
|
||||
#define __ server_symbols()->
|
||||
|
||||
will transform above, overly verbose lines to more readable
|
||||
|
||||
T_(Server) server = __ kotlin.demo.Server.Server("the server");
|
||||
|
||||
`_type()` function will return opaque type pointer, which could be checked with `IsInstance()` operation, like
|
||||
|
||||
__ IsInstance(ref.pinned, __ kotlin.demo.Server._type())
|
||||
@@ -1,15 +0,0 @@
|
||||
setlocal
|
||||
set DIR=.
|
||||
|
||||
if defined KONAN_HOME (
|
||||
set "PATH=%KONAN_HOME%\bin;%PATH%"
|
||||
) else (
|
||||
set "PATH=..\..\dist\bin;..\..\bin;%PATH%"
|
||||
)
|
||||
kotlinc-native -p dynamic src/main/kotlin/Server.kt -o server
|
||||
|
||||
rem Prepare MSVC build environment, and .lib file for linking with our .dll.
|
||||
SET VS90COMNTOOLS=%VS140COMNTOOLS%
|
||||
\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\lib.exe" /def:server.def /out:server.lib /machine:X64
|
||||
|
||||
python src/main/python/setup.py install
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
|
||||
if [ -z "$KONAN_HOME" ]; then
|
||||
PATH="$DIR/../../dist/bin:$DIR/../../bin:$PATH"
|
||||
else
|
||||
PATH="$KONAN_HOME/bin:$PATH"
|
||||
fi
|
||||
|
||||
KONAN_USER_DIR=${KONAN_DATA_DIR:-"$HOME/.konan"}
|
||||
KONAN_DEPS="$KONAN_USER_DIR/dependencies"
|
||||
|
||||
# python3 shall work as well.
|
||||
PYTHON=python
|
||||
|
||||
mkdir -p $DIR/build
|
||||
cd $DIR/build
|
||||
|
||||
kotlinc-native -p dynamic $DIR/src/main/kotlin/Server.kt -o server
|
||||
|
||||
cd $DIR
|
||||
sudo -S $PYTHON ${DIR}/src/main/python/setup.py install
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include "server_api.h"
|
||||
|
||||
#define __ server_symbols()->
|
||||
#define T_(name) server_kref_demo_ ## name
|
||||
|
||||
// Note, that as we cache this in the global, and Kotlin/Native object references
|
||||
// are currently thread local, we make this global a TLS variable.
|
||||
#ifdef _MSC_VER
|
||||
#define TLSVAR __declspec(thread)
|
||||
#else
|
||||
#define TLSVAR __thread
|
||||
#endif
|
||||
|
||||
static TLSVAR server_kref_demo_Server server = { 0 };
|
||||
|
||||
static T_(Server) getServer(void) {
|
||||
if (!server.pinned) {
|
||||
server = __ kotlin.root.demo.Server.Server("the server");
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
static T_(Session) getSession(PyObject* args) {
|
||||
T_(Session) result = { 0 };
|
||||
long long pinned;
|
||||
if (PyArg_ParseTuple(args, "L", &pinned)) {
|
||||
result.pinned = (void*)(uintptr_t)pinned;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyObject* open_session(PyObject* self, PyObject* args) {
|
||||
PyObject *result = NULL;
|
||||
char* string_arg = NULL;
|
||||
int int_arg = 0;
|
||||
if (PyArg_ParseTuple(args, "is", &int_arg, &string_arg)) {
|
||||
T_(Session) session = __ kotlin.root.demo.Session.Session(string_arg, int_arg);
|
||||
result = Py_BuildValue("L", session.pinned);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyObject* close_session(PyObject* self, PyObject* args) {
|
||||
T_(Session) session = getSession(args);
|
||||
__ DisposeStablePointer(session.pinned);
|
||||
__ DisposeStablePointer(getServer().pinned);
|
||||
server.pinned = 0;
|
||||
return Py_BuildValue("L", 0);
|
||||
}
|
||||
|
||||
static PyObject* greet_server(PyObject* self, PyObject* args) {
|
||||
T_(Server) server = getServer();
|
||||
T_(Session) session = getSession(args);
|
||||
const char* string = __ kotlin.root.demo.Server.greet(server, session);
|
||||
PyObject* result = Py_BuildValue("s", string);
|
||||
__ DisposeString(string);
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyObject* concat_server(PyObject* self, PyObject* args) {
|
||||
long long session_arg;
|
||||
char* string_arg1 = NULL;
|
||||
char* string_arg2 = NULL;
|
||||
PyObject* result = NULL;
|
||||
|
||||
if (PyArg_ParseTuple(args, "Lss", &session_arg, &string_arg1, &string_arg2)) {
|
||||
T_(Server) server = getServer();
|
||||
T_(Session) session = { (void*)(uintptr_t)session_arg };
|
||||
const char* string = __ kotlin.root.demo.Server.concat(server, session, string_arg1, string_arg2);
|
||||
result = Py_BuildValue("s", string);
|
||||
__ DisposeString(string);
|
||||
} else {
|
||||
result = Py_BuildValue("s", NULL);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyObject* add_server(PyObject* self, PyObject* args) {
|
||||
long long session_arg;
|
||||
int int_arg1 = 0;
|
||||
int int_arg2 = 0;
|
||||
PyObject* result = NULL;
|
||||
|
||||
if (PyArg_ParseTuple(args, "Lii", &session_arg, &int_arg1, &int_arg2)) {
|
||||
T_(Server) server = getServer();
|
||||
T_(Session) session = { (void*)(uintptr_t)session_arg };
|
||||
int sum = __ kotlin.root.demo.Server.add(server, session, int_arg1, int_arg2);
|
||||
result = Py_BuildValue("i", sum);
|
||||
} else {
|
||||
result = Py_BuildValue("i", 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyMethodDef kotlin_bridge_funcs[] = {
|
||||
{ "open_session", (PyCFunction)open_session, METH_VARARGS, "Opens a session" },
|
||||
{ "close_session", (PyCFunction)close_session, METH_VARARGS, "Closes the session" },
|
||||
{ "greet_server", (PyCFunction)greet_server, METH_VARARGS, "Greeting service" },
|
||||
{ "concat_server", (PyCFunction)concat_server, METH_VARARGS, "Concatenation service" },
|
||||
{ "add_server", (PyCFunction)add_server, METH_VARARGS, "Addition service" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
|
||||
struct module_state {
|
||||
server_kref_demo_Server server;
|
||||
};
|
||||
|
||||
static int kotlin_bridge_traverse(PyObject *m, visitproc visit, void *arg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kotlin_bridge_clear(PyObject *m) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct PyModuleDef moduledef = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"kotlin_bridge",
|
||||
NULL,
|
||||
sizeof(struct module_state),
|
||||
kotlin_bridge_funcs,
|
||||
NULL,
|
||||
kotlin_bridge_traverse,
|
||||
kotlin_bridge_clear,
|
||||
NULL
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC PyInit_kotlin_bridge(void) {
|
||||
PyObject *module = PyModule_Create(&moduledef);
|
||||
return module;
|
||||
}
|
||||
#else
|
||||
void initkotlin_bridge(void) {
|
||||
Py_InitModule3("kotlin_bridge", kotlin_bridge_funcs, "Kotlin/Native example module");
|
||||
}
|
||||
#endif
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package demo
|
||||
|
||||
class Session(val name: String, val number: Int)
|
||||
|
||||
class Server(val prefix: String) {
|
||||
fun greet(session: Session) = "$prefix: Hello from Kotlin/Native in ${session}"
|
||||
fun concat(session: Session, a: String, b: String) = "$prefix: $a $b in ${session}"
|
||||
fun add(session: Session, a: Int, b: Int) = a + b + session.number
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
# that can be found in the license/LICENSE.txt file.
|
||||
#
|
||||
|
||||
import kotlin_bridge
|
||||
|
||||
session = kotlin_bridge.open_session(239, 'konan')
|
||||
|
||||
message = kotlin_bridge.greet_server(session)
|
||||
print("Greet '{}'".format(message))
|
||||
|
||||
message = kotlin_bridge.concat_server(session, "Coding", "fun")
|
||||
print("Concat '{}'".format(message))
|
||||
|
||||
message = kotlin_bridge.add_server(session, 1, 60)
|
||||
print("Sum '{}'".format(message))
|
||||
|
||||
|
||||
kotlin_bridge.close_session(session)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
# that can be found in the license/LICENSE.txt file.
|
||||
#
|
||||
|
||||
from distutils.core import setup, Extension
|
||||
|
||||
setup(name='kotlin_bridge',
|
||||
version='1.0',
|
||||
maintainer = 'JetBrains',
|
||||
maintainer_email = 'info@jetbrains.com',
|
||||
author = 'JetBrains',
|
||||
author_email = 'info@jetbrains.com',
|
||||
description = 'Kotlin/Native Python bridge',
|
||||
long_description = 'Using Kotlin/Native from Python example',
|
||||
|
||||
# data_files=[("/Library/Python/2.7/site-packages/", ['libserver.dylib'])],
|
||||
|
||||
ext_modules=[
|
||||
Extension('kotlin_bridge',
|
||||
include_dirs = ['./build'],
|
||||
libraries = ['server'],
|
||||
library_dirs = ['./build'],
|
||||
depends = ['server_api.h'],
|
||||
sources = ['src/main/c/kotlin_bridge.c']
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# On macOS, after install you may want to copy libserver.dylib to Python's extension directory,
|
||||
# and do something like
|
||||
# sudo install_name_tool /Library/Python/2.7/site-packages/kotlin_bridge.so -change libserver.dylib @loader_path/libserver.dylib
|
||||
# This way libserver.dylib could be loaded from extension's directory.
|
||||
@@ -19,12 +19,9 @@ if (isMacos || isLinux || isWindows) {
|
||||
include(":csvparser")
|
||||
include(":curl")
|
||||
include(":echoServer")
|
||||
include(":gitchurn")
|
||||
include(":globalState")
|
||||
include(":gtk")
|
||||
include(":html5Canvas")
|
||||
include(":libcurl")
|
||||
include(":tetris")
|
||||
include(":videoplayer")
|
||||
include(":workers")
|
||||
include(":coverage")
|
||||
@@ -33,7 +30,6 @@ if (isMacos || isLinux || isWindows) {
|
||||
if (isMacos || isLinux) {
|
||||
include(":nonBlockingEchoServer")
|
||||
include(":tensorflow")
|
||||
include(":torch")
|
||||
}
|
||||
|
||||
if (isMacos) {
|
||||
@@ -41,7 +37,6 @@ if (isMacos) {
|
||||
include(":opengl")
|
||||
include(":uikit")
|
||||
include(":watchos")
|
||||
include(":simd")
|
||||
}
|
||||
|
||||
if (isWindows) {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
macosX64 {
|
||||
binaries {
|
||||
executable()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import platform.Accelerate.*
|
||||
|
||||
// Custom print
|
||||
fun Vector128.toStringHex(): String {
|
||||
return "0x" + (0 until 16).map { getUByteAt(it).toString(16) }.joinToString("")
|
||||
}
|
||||
|
||||
// Custom print
|
||||
fun Vector128.toStringFloat(): String {
|
||||
return "(${(0 until 4).map { getFloatAt(it).toString() }.joinToString(", ")})"
|
||||
}
|
||||
|
||||
|
||||
fun main() {
|
||||
|
||||
// Accessors
|
||||
val vf4 = vectorOf(1f, 3.162f, 10f, 31f)
|
||||
println(vf4)
|
||||
println(vf4.toStringFloat())
|
||||
println(vf4.toStringHex())
|
||||
println(vf4.getFloatAt(1))
|
||||
println(vf4.getIntAt(0))
|
||||
println(vf4.getByteAt(3))
|
||||
// Illegal access (out of bounds)
|
||||
try {
|
||||
println(vf4.getIntAt(4))
|
||||
println("FAILED")
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
println("Handling $e")
|
||||
}
|
||||
|
||||
// Assignment and equality
|
||||
var x1 = vectorOf(-1f, 0f, 0f, -7f)
|
||||
val y1 = vectorOf(-1f, 0f, 0f, -7f)
|
||||
var x2 = vectorOf(1f, 4f, 3f, 7f)
|
||||
println("(x1 == y1) is ${(x1 == y1)}")
|
||||
println("(x1.equals(y1)) is ${(x1.equals(y1))}")
|
||||
println("(x1 == x2) is ${(x1 == x2)}")
|
||||
x1 = x2
|
||||
println("Now (x1 == x1) is ${(x1 == x1)}")
|
||||
|
||||
|
||||
// Using library function (MacOS Accelerate framework)
|
||||
val sum = vS128Add(vectorOf(1,2,3,4), vectorOf(4,3,2,1))
|
||||
println(sum)
|
||||
// More Accelerate framework
|
||||
val q = vectorOf(1f, 9f, 25f, 49f)
|
||||
val sq = vsqrtf(q)
|
||||
println("vsqrtf$q = ${sq.toStringFloat()}")
|
||||
val f4 = vectorOf(1f, 3.162f, 10f, 31f)
|
||||
println("vlog10f($f4) = ${vlog10f(vf4).toStringFloat()}")
|
||||
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
# Tetris game
|
||||
|
||||
This example shows implementation of simple Tetris game using SDL
|
||||
(Simple DirectMedia Layer) library for rendering. SDL allows easy development
|
||||
of cross-platform game and multimedia applications.
|
||||
|
||||
Install SDL2 development files (see https://www.libsdl.org/download-2.0.php). For Mac -
|
||||
copy `SDL2.framework` to `$HOME/Library/Frameworks`. For Debian-like Linux -
|
||||
use `apt-get install libsdl2-dev`.
|
||||
For Windows - `pacman -S mingw-w64-x86_64-SDL2 mingw-w64-i686-SDL2` in MSYS2 console. If you do not have MSYS2
|
||||
installed - install it first as described in http://www.msys2.org
|
||||
|
||||
To build Tetris application for your host platform use `../gradlew assemble`.
|
||||
|
||||
Note that SDL2 must be installed on the host.
|
||||
|
||||
Now you can run the game using `../gradlew runReleaseExecutableTetris` or directly with
|
||||
|
||||
./build/bin/tetris/main/release/executable/tetris.kexe
|
||||
|
||||
During build process compilation script creates interoperability bindings to SDL2, using SDL C headers,
|
||||
and then compiles an application with the produced bindings.
|
||||
|
||||
To deploy executable to iPhone device take Info.plist, then use XCode and your own private signing identity.
|
||||
|
||||
To run on Raspberry Pi one need to install SDL package with `apt-get install libsdl2-2.0.0` on the Pi.
|
||||
Also GLES2 renderer is recommended (use `SDL_RENDER_DRIVER=opengles2 ./Tetris.kexe`).
|
||||
|
||||
For Windows `set SDL_RENDER_DRIVER=software` may be needed on some machines.
|
||||
|
||||
Note: There is a known issue with SDL2 library on Mac OS X 10.14 Mojave. Window may render black until
|
||||
it is dragged. See https://bugzilla.libsdl.org/show_bug.cgi?id=4272
|
||||
@@ -1,119 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
val kotlinNativeDataPath = System.getenv("KONAN_DATA_DIR")?.let { File(it) }
|
||||
?: File(System.getProperty("user.home")).resolve(".konan")
|
||||
val mingw64Path = File(System.getenv("MINGW64_DIR") ?: "C:/msys64/mingw64")
|
||||
val mingw32Path = File(System.getenv("MINGW32_DIR") ?: "C:/msys64/mingw32")
|
||||
|
||||
kotlin {
|
||||
val hostOs = System.getProperty("os.name")
|
||||
if (hostOs == "Mac OS X") {
|
||||
macosX64()
|
||||
}
|
||||
if (hostOs == "Linux") {
|
||||
linuxX64()
|
||||
}
|
||||
if (hostOs.startsWith("Windows")) {
|
||||
mingwX64()
|
||||
mingwX86()
|
||||
}
|
||||
|
||||
targets.withType<KotlinNativeTarget> {
|
||||
sourceSets["${targetName}Main"].apply {
|
||||
kotlin.srcDir("src/tetrisMain/kotlin")
|
||||
}
|
||||
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.tetris.main"
|
||||
|
||||
// Compile Windows Resources
|
||||
if (preset == presets["mingwX64"] || preset == presets["mingwX86"]) {
|
||||
val taskName = linkTaskName.replaceFirst("link", "windres")
|
||||
val inFile = File("src/tetrisMain/resources/Tetris.rc")
|
||||
val outFile = buildDir.resolve("processedResources/$taskName.res")
|
||||
val windresTask = tasks.register<Exec>(taskName) {
|
||||
val llvmDir = when (preset) {
|
||||
presets["mingwX86"] -> kotlinNativeDataPath.resolve(
|
||||
"dependencies/msys2-mingw-w64-i686-2/bin")
|
||||
presets["mingwX64"] -> kotlinNativeDataPath.resolve(
|
||||
"dependencies/msys2-mingw-w64-x86_64-2/bin")
|
||||
else -> throw GradleException("Unsupported presets")
|
||||
}.toString()
|
||||
inputs.file(inFile)
|
||||
outputs.file(outFile)
|
||||
commandLine("$llvmDir/windres", inFile, "-O", "coff", "-o", outFile)
|
||||
environment("PATH", "$llvmDir;${System.getenv("PATH")}")
|
||||
dependsOn(compilation.compileKotlinTask)
|
||||
}
|
||||
linkTask.dependsOn(windresTask)
|
||||
linkerOpts(outFile.toString())
|
||||
}
|
||||
|
||||
when (preset) {
|
||||
presets["macosX64"] -> linkerOpts("-L/opt/local/lib", "-L/usr/local/lib", "-lSDL2")
|
||||
presets["linuxX64"] -> linkerOpts("-L/usr/lib64", "-L/usr/lib/x86_64-linux-gnu", "-lSDL2")
|
||||
presets["mingwX64"] -> linkerOpts(
|
||||
"-L${mingw64Path.resolve("lib")}",
|
||||
"-Wl,-Bstatic",
|
||||
"-lstdc++",
|
||||
"-static",
|
||||
"-lSDL2",
|
||||
"-limm32",
|
||||
"-lole32",
|
||||
"-loleaut32",
|
||||
"-lversion",
|
||||
"-lwinmm",
|
||||
"-lsetupapi",
|
||||
"-mwindows"
|
||||
)
|
||||
presets["mingwX86"] -> linkerOpts(
|
||||
"-L${mingw32Path.resolve("lib")}",
|
||||
"-Wl,-Bstatic",
|
||||
"-lstdc++",
|
||||
"-static",
|
||||
"-lSDL2",
|
||||
"-limm32",
|
||||
"-lole32",
|
||||
"-loleaut32",
|
||||
"-lversion",
|
||||
"-lwinmm",
|
||||
"-lsetupapi",
|
||||
"-mwindows"
|
||||
)
|
||||
}
|
||||
|
||||
val distTaskName = linkTaskName.replaceFirst("link", "dist")
|
||||
val distTask = tasks.register<Copy>(distTaskName) {
|
||||
from("src/tetrisMain/resources")
|
||||
into(linkTask.outputFile.get().parentFile)
|
||||
exclude("*.rc")
|
||||
if (!konanTarget.family.isAppleFamily) {
|
||||
exclude("*.plist")
|
||||
}
|
||||
dependsOn(linkTask)
|
||||
}
|
||||
tasks["assemble"].dependsOn(distTask)
|
||||
|
||||
runTask?.workingDir(project.provider { outputDirectory })
|
||||
}
|
||||
}
|
||||
|
||||
compilations["main"].cinterops {
|
||||
val sdl by creating {
|
||||
when (preset) {
|
||||
presets["macosX64"] -> includeDirs("/opt/local/include/SDL2", "/usr/local/include/SDL2")
|
||||
presets["linuxX64"] -> includeDirs("/usr/include", "/usr/include/x86_64-linux-gnu", "/usr/include/SDL2")
|
||||
presets["mingwX64"] -> includeDirs(mingw64Path.resolve("include/SDL2"))
|
||||
presets["mingwX86"] -> includeDirs(mingw32Path.resolve("include/SDL2"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compilations["main"].enableEndorsedLibs = true
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
@@ -1,9 +0,0 @@
|
||||
headers = SDL.h stdlib.h time.h
|
||||
entryPoint = SDL_main
|
||||
|
||||
headerFilter = SDL* stdlib.h time.h
|
||||
|
||||
compilerOpts = -D_POSIX_SOURCE
|
||||
compilerOpts.osx =
|
||||
compilerOpts.linux = -D_REENTRANT
|
||||
compilerOpts.ios =
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.tetris
|
||||
|
||||
import platform.posix.*
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
object Config {
|
||||
var width: Int = 10
|
||||
private set
|
||||
var height: Int = 20
|
||||
private set
|
||||
var startLevel = 0
|
||||
private set
|
||||
|
||||
init {
|
||||
val file = fopen("config.txt", "r")
|
||||
if (file != null) {
|
||||
try {
|
||||
val buffer = ByteArray(2 * 1024)
|
||||
while (true) {
|
||||
val nextLine = fgets(buffer.refTo(0), buffer.size, file)?.toKString()
|
||||
if (nextLine == null || nextLine.isEmpty()) break
|
||||
val records = nextLine.split('=')
|
||||
if (records.size != 2) continue
|
||||
val key = records[0].trim()
|
||||
val value = records[1].trim()
|
||||
when (key) {
|
||||
"width" -> width = value.toInt()
|
||||
"height" -> height = value.toInt()
|
||||
"startLevel" -> startLevel = value.toInt()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fclose(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,486 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.tetris
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.*
|
||||
import sdl.*
|
||||
|
||||
fun get_SDL_Error() = SDL_GetError()!!.toKString()
|
||||
|
||||
fun sleep(millis: Int) {
|
||||
SDL_Delay(millis.toUInt())
|
||||
}
|
||||
|
||||
class SDL_Visualizer(val width: Int, val height: Int): GameFieldVisualizer, UserInput {
|
||||
private val CELL_SIZE = 20
|
||||
private val COLORS = 10
|
||||
private val CELLS_WIDTH = COLORS * CELL_SIZE
|
||||
private val CELLS_HEIGHT = 3 * CELL_SIZE
|
||||
private val SYMBOL_SIZE = 21
|
||||
private val INFO_MARGIN = 10
|
||||
private val MARGIN = 2
|
||||
private val BORDER_WIDTH = 18
|
||||
private val INFO_SPACE_WIDTH = SYMBOL_SIZE * (2 + 8)
|
||||
private val LINES_LABEL_WIDTH = 104
|
||||
private val SCORE_LABEL_WIDTH = 107
|
||||
private val LEVEL_LABEL_WIDTH = 103
|
||||
private val NEXT_LABEL_WIDTH = 85
|
||||
private val TETRISES_LABEL_WIDTH = 162
|
||||
|
||||
private var ratio: Float
|
||||
|
||||
private fun stretch(value: Int) = (value.toFloat() * ratio + 0.5).toInt()
|
||||
|
||||
inner class GamePadButtons(width: Int, height: Int, gamePadHeight: Int) {
|
||||
val MOVE_BUTTON_SIZE = 50
|
||||
val ROTATE_BUTTON_SIZE = 80
|
||||
val BUTTONS_MARGIN = 25
|
||||
|
||||
val arena = Arena()
|
||||
val leftRect: SDL_Rect
|
||||
val rightRect: SDL_Rect
|
||||
val downRect: SDL_Rect
|
||||
val dropRect: SDL_Rect
|
||||
val rotateRect: SDL_Rect
|
||||
|
||||
init {
|
||||
val moveButtonsWidth = 3 * MOVE_BUTTON_SIZE + 2 * BUTTONS_MARGIN + BUTTONS_MARGIN
|
||||
val x = (width - moveButtonsWidth - ROTATE_BUTTON_SIZE) / 2 - MOVE_BUTTON_SIZE
|
||||
val y2 = (gamePadHeight - 2 * MOVE_BUTTON_SIZE - BUTTONS_MARGIN) / 2
|
||||
leftRect = arena.alloc<SDL_Rect>()
|
||||
leftRect.w = MOVE_BUTTON_SIZE
|
||||
leftRect.h = MOVE_BUTTON_SIZE
|
||||
leftRect.x = x
|
||||
leftRect.y = height - gamePadHeight + y2 + MOVE_BUTTON_SIZE + BUTTONS_MARGIN
|
||||
|
||||
downRect = arena.alloc<SDL_Rect>()
|
||||
downRect.w = MOVE_BUTTON_SIZE
|
||||
downRect.h = MOVE_BUTTON_SIZE
|
||||
downRect.x = x + MOVE_BUTTON_SIZE + BUTTONS_MARGIN
|
||||
downRect.y = leftRect.y
|
||||
|
||||
dropRect = arena.alloc<SDL_Rect>()
|
||||
dropRect.w = MOVE_BUTTON_SIZE
|
||||
dropRect.h = MOVE_BUTTON_SIZE
|
||||
dropRect.x = downRect.x
|
||||
dropRect.y = height - gamePadHeight + y2
|
||||
|
||||
rightRect = arena.alloc<SDL_Rect>()
|
||||
rightRect.w = MOVE_BUTTON_SIZE
|
||||
rightRect.h = MOVE_BUTTON_SIZE
|
||||
rightRect.x = x + 2 * MOVE_BUTTON_SIZE + 2 * BUTTONS_MARGIN
|
||||
rightRect.y = height - gamePadHeight + y2 + MOVE_BUTTON_SIZE + BUTTONS_MARGIN
|
||||
|
||||
rotateRect = arena.alloc<SDL_Rect>()
|
||||
rotateRect.w = ROTATE_BUTTON_SIZE
|
||||
rotateRect.h = ROTATE_BUTTON_SIZE
|
||||
rotateRect.x = x + moveButtonsWidth
|
||||
rotateRect.y = height - gamePadHeight + y2 - BUTTONS_MARGIN
|
||||
}
|
||||
|
||||
fun getCommandAt(x: Int, y: Int): UserCommand? {
|
||||
return when {
|
||||
inside(leftRect, x, y) -> UserCommand.LEFT
|
||||
inside(rightRect, x, y) -> UserCommand.RIGHT
|
||||
inside(downRect, x, y) -> UserCommand.DOWN
|
||||
inside(dropRect, x, y) -> UserCommand.DROP
|
||||
inside(rotateRect, x, y) -> UserCommand.ROTATE
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun inside(rect: SDL_Rect, x: Int, y: Int): Boolean {
|
||||
return x >= stretch(rect.x) && x <= stretch(rect.x + rect.w)
|
||||
&& y >= stretch(rect.y) && y <= stretch(rect.y + rect.h)
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
arena.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private val field: Field = Array<ByteArray>(height) { ByteArray(width) }
|
||||
private val nextPieceField: Field = Array<ByteArray>(4) { ByteArray(4) }
|
||||
private var linesCleared: Int = 0
|
||||
private var level: Int = 0
|
||||
private var score: Int = 0
|
||||
private var tetrises: Int = 0
|
||||
|
||||
private var displayWidth: Int = 0
|
||||
private var displayHeight: Int = 0
|
||||
private val fieldWidth: Int
|
||||
private val fieldHeight: Int
|
||||
private var windowX: Int
|
||||
private var windowY: Int
|
||||
private val window: CPointer<SDL_Window>
|
||||
private val renderer: CPointer<SDL_Renderer>
|
||||
private val texture: CPointer<SDL_Texture>
|
||||
private val gamePadButtons: GamePadButtons?
|
||||
private val platform: String
|
||||
|
||||
init {
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
||||
throw Error("SDL_Init Error: ${get_SDL_Error()}")
|
||||
}
|
||||
|
||||
platform = SDL_GetPlatform()!!.toKString()
|
||||
|
||||
memScoped {
|
||||
val displayMode = alloc<SDL_DisplayMode>()
|
||||
if (SDL_GetCurrentDisplayMode(0, displayMode.ptr.reinterpret()) != 0) {
|
||||
println("SDL_GetCurrentDisplayMode Error: ${get_SDL_Error()}")
|
||||
SDL_Quit()
|
||||
throw Error()
|
||||
}
|
||||
displayWidth = displayMode.w
|
||||
displayHeight = displayMode.h
|
||||
}
|
||||
fieldWidth = width * (CELL_SIZE + MARGIN) + MARGIN + BORDER_WIDTH * 2
|
||||
fieldHeight = height * (CELL_SIZE + MARGIN) + MARGIN + BORDER_WIDTH * 2
|
||||
var windowWidth = fieldWidth + INFO_SPACE_WIDTH
|
||||
var windowHeight: Int
|
||||
if (platform == "iOS") {
|
||||
val gamePadHeight = (displayHeight * windowWidth - fieldHeight * displayWidth) / displayWidth
|
||||
windowHeight = fieldHeight + gamePadHeight
|
||||
gamePadButtons = GamePadButtons(windowWidth, windowHeight, gamePadHeight)
|
||||
windowX = 0
|
||||
windowY = 0
|
||||
ratio = displayHeight.toFloat() / windowHeight
|
||||
windowWidth = displayWidth
|
||||
windowHeight = displayHeight
|
||||
} else {
|
||||
windowHeight = fieldHeight
|
||||
gamePadButtons = null
|
||||
windowX = (displayWidth - windowWidth) / 2
|
||||
windowY = (displayHeight - windowHeight) / 2
|
||||
ratio = 1.0f
|
||||
}
|
||||
val window = SDL_CreateWindow("Tetris", windowX, windowY, windowWidth, windowHeight,
|
||||
SDL_WINDOW_SHOWN or SDL_WINDOW_ALLOW_HIGHDPI)
|
||||
if (window == null) {
|
||||
println("SDL_CreateWindow Error: ${get_SDL_Error()}")
|
||||
SDL_Quit()
|
||||
throw Error()
|
||||
}
|
||||
this.window = window
|
||||
|
||||
val renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED or SDL_RENDERER_PRESENTVSYNC)
|
||||
if (renderer == null) {
|
||||
SDL_DestroyWindow(window)
|
||||
println("SDL_CreateRenderer Error: ${get_SDL_Error()}")
|
||||
SDL_Quit()
|
||||
throw Error()
|
||||
}
|
||||
this.renderer = renderer
|
||||
|
||||
memScoped {
|
||||
val realWidth = alloc<IntVar>()
|
||||
val realHeight = alloc<IntVar>()
|
||||
SDL_GetRendererOutputSize(renderer, realWidth.ptr, realHeight.ptr)
|
||||
if (platform != "iOS" && windowHeight != realHeight.value) {
|
||||
println("DPI differs ${realWidth.value} x ${realHeight.value} vs $windowWidth x $windowHeight")
|
||||
ratio = realHeight.value.toFloat() / windowHeight
|
||||
}
|
||||
}
|
||||
|
||||
texture = loadImage(window, renderer, findFile("tetris_all.bmp"))
|
||||
}
|
||||
|
||||
private fun findFile(name: String): String {
|
||||
memScoped {
|
||||
val dirs = listOf(".", SDL_GetBasePath()?.toKString() ?: "/")
|
||||
val statBuffer = alloc<stat>()
|
||||
dirs.forEach {
|
||||
val candidate = "$it/$name"
|
||||
if (stat(candidate, statBuffer.ptr) == 0) return candidate
|
||||
}
|
||||
throw Error("name not found")
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadImage(win: CPointer<SDL_Window>, ren: CPointer<SDL_Renderer>, imagePath: String): CPointer<SDL_Texture> {
|
||||
val bmp = SDL_LoadBMP_RW(SDL_RWFromFile(imagePath, "rb"), 1);
|
||||
if (bmp == null) {
|
||||
SDL_DestroyRenderer(ren)
|
||||
SDL_DestroyWindow(win)
|
||||
println("SDL_LoadBMP_RW Error: ${get_SDL_Error()}")
|
||||
SDL_Quit()
|
||||
throw Error()
|
||||
}
|
||||
|
||||
val tex = SDL_CreateTextureFromSurface(ren, bmp)
|
||||
SDL_FreeSurface(bmp)
|
||||
if (tex == null) {
|
||||
SDL_DestroyRenderer(ren)
|
||||
SDL_DestroyWindow(win)
|
||||
println("SDL_CreateTextureFromSurface Error: ${get_SDL_Error()}")
|
||||
SDL_Quit()
|
||||
throw Error()
|
||||
}
|
||||
return tex
|
||||
}
|
||||
|
||||
override fun drawCell(x: Int, y: Int, cell: Byte) {
|
||||
field[x][y] = cell
|
||||
}
|
||||
|
||||
override fun drawNextPieceCell(x: Int, y: Int, cell: Byte) {
|
||||
nextPieceField[x][y] = cell
|
||||
}
|
||||
|
||||
override fun setInfo(linesCleared: Int, level: Int, score: Int, tetrises: Int) {
|
||||
this.linesCleared = linesCleared
|
||||
this.level = level
|
||||
this.score = score
|
||||
this.tetrises = tetrises
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
SDL_RenderClear(renderer)
|
||||
drawField()
|
||||
drawInfo()
|
||||
drawNextPiece()
|
||||
drawGamePad()
|
||||
SDL_RenderPresent(renderer)
|
||||
}
|
||||
|
||||
private fun drawBorder(topLeftX: Int, topLeftY: Int, width: Int, height: Int) {
|
||||
// Upper-left corner.
|
||||
var srcX = CELLS_WIDTH
|
||||
var srcY = 0
|
||||
var destX = topLeftX
|
||||
var destY = topLeftY
|
||||
copyRect(srcX, srcY, destX, destY, BORDER_WIDTH + MARGIN, BORDER_WIDTH)
|
||||
|
||||
// Upper margin.
|
||||
srcX += BORDER_WIDTH + MARGIN
|
||||
destX += BORDER_WIDTH + MARGIN
|
||||
for (i in 0..width - 1) {
|
||||
copyRect(srcX, srcY, destX, destY, CELL_SIZE + MARGIN, BORDER_WIDTH)
|
||||
destX += CELL_SIZE + MARGIN
|
||||
}
|
||||
|
||||
// Upper-right corner.
|
||||
srcX += CELL_SIZE + MARGIN
|
||||
copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, BORDER_WIDTH + MARGIN)
|
||||
|
||||
// Right margin.
|
||||
srcY += BORDER_WIDTH + MARGIN
|
||||
destY += BORDER_WIDTH + MARGIN
|
||||
for (j in 0..height - 1) {
|
||||
copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, CELL_SIZE + MARGIN)
|
||||
destY += CELL_SIZE + MARGIN
|
||||
}
|
||||
|
||||
// Left margin.
|
||||
srcX = CELLS_WIDTH
|
||||
srcY = BORDER_WIDTH
|
||||
destX = topLeftX
|
||||
destY = topLeftY + BORDER_WIDTH
|
||||
for (j in 0..height - 1) {
|
||||
copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, CELL_SIZE + MARGIN)
|
||||
destY += CELL_SIZE + MARGIN
|
||||
}
|
||||
|
||||
// Left-down corner.
|
||||
srcY += CELL_SIZE + MARGIN
|
||||
copyRect(srcX, srcY, destX, destY, BORDER_WIDTH, BORDER_WIDTH + MARGIN)
|
||||
|
||||
// Down marign.
|
||||
srcX += BORDER_WIDTH
|
||||
srcY += MARGIN
|
||||
destX += BORDER_WIDTH
|
||||
destY += MARGIN
|
||||
for (i in 0..width - 1) {
|
||||
copyRect(srcX, srcY, destX, destY, CELL_SIZE + MARGIN, BORDER_WIDTH)
|
||||
destX += CELL_SIZE + MARGIN
|
||||
|
||||
}
|
||||
// Right-down corner.
|
||||
srcX += CELL_SIZE + MARGIN
|
||||
copyRect(srcX, srcY, destX, destY, BORDER_WIDTH + MARGIN, BORDER_WIDTH)
|
||||
}
|
||||
|
||||
private fun drawField() {
|
||||
drawField(field = field,
|
||||
topLeftX = 0,
|
||||
topLeftY = 0,
|
||||
width = width,
|
||||
height = height)
|
||||
}
|
||||
|
||||
private fun drawNextPiece() {
|
||||
drawInt(labelSrcX = LEVEL_LABEL_WIDTH,
|
||||
labelSrcY = CELLS_HEIGHT + SYMBOL_SIZE,
|
||||
labelDestX = fieldWidth + SYMBOL_SIZE,
|
||||
labelDestY = getInfoY(5),
|
||||
labelWidth = NEXT_LABEL_WIDTH,
|
||||
totalDigits = 0,
|
||||
value = 0)
|
||||
drawField(field = nextPieceField,
|
||||
topLeftX = fieldWidth + SYMBOL_SIZE,
|
||||
topLeftY = getInfoY(6),
|
||||
width = 4,
|
||||
height = 4)
|
||||
}
|
||||
|
||||
private fun drawField(field: Field, topLeftX: Int, topLeftY: Int, width: Int, height: Int) {
|
||||
drawBorder(topLeftX = topLeftX,
|
||||
topLeftY = topLeftY,
|
||||
width = width,
|
||||
height = height)
|
||||
for (i in 0..height - 1)
|
||||
for (j in 0..width - 1) {
|
||||
val cell = field[i][j].toInt()
|
||||
if (cell == 0) continue
|
||||
copyRect(srcX = (level % COLORS) * CELL_SIZE,
|
||||
srcY = (3 - cell) * CELL_SIZE,
|
||||
destX = topLeftX + BORDER_WIDTH + MARGIN + j * (CELL_SIZE + MARGIN),
|
||||
destY = topLeftY + BORDER_WIDTH + MARGIN + i * (CELL_SIZE + MARGIN),
|
||||
width = CELL_SIZE,
|
||||
height = CELL_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawInfo() {
|
||||
drawInt(labelSrcX = LINES_LABEL_WIDTH,
|
||||
labelSrcY = CELLS_HEIGHT,
|
||||
labelDestX = fieldWidth + SYMBOL_SIZE,
|
||||
labelDestY = getInfoY(0),
|
||||
labelWidth = SCORE_LABEL_WIDTH,
|
||||
totalDigits = 6,
|
||||
value = score)
|
||||
drawInt(labelSrcX = 0,
|
||||
labelSrcY = CELLS_HEIGHT,
|
||||
labelDestX = fieldWidth + SYMBOL_SIZE,
|
||||
labelDestY = getInfoY(1),
|
||||
labelWidth = LINES_LABEL_WIDTH,
|
||||
totalDigits = 3,
|
||||
value = linesCleared)
|
||||
drawInt(labelSrcX = 0,
|
||||
labelSrcY = CELLS_HEIGHT + SYMBOL_SIZE,
|
||||
labelDestX = fieldWidth + SYMBOL_SIZE,
|
||||
labelDestY = getInfoY(2),
|
||||
labelWidth = LEVEL_LABEL_WIDTH,
|
||||
totalDigits = 2,
|
||||
value = level)
|
||||
drawInt(labelSrcX = 0,
|
||||
labelSrcY = CELLS_HEIGHT + SYMBOL_SIZE * 2,
|
||||
labelDestX = fieldWidth + SYMBOL_SIZE,
|
||||
labelDestY = getInfoY(3),
|
||||
labelWidth = TETRISES_LABEL_WIDTH,
|
||||
totalDigits = 2,
|
||||
value = tetrises)
|
||||
}
|
||||
|
||||
private fun getInfoY(line: Int): Int {
|
||||
return SYMBOL_SIZE * (2 * line + 1) + INFO_MARGIN * line
|
||||
}
|
||||
|
||||
private fun drawInt(labelSrcX: Int, labelSrcY: Int, labelDestX: Int, labelDestY: Int,
|
||||
labelWidth: Int, totalDigits: Int, value: Int) {
|
||||
copyRect(srcX = labelSrcX,
|
||||
srcY = labelSrcY,
|
||||
destX = labelDestX,
|
||||
destY = labelDestY,
|
||||
width = labelWidth,
|
||||
height = SYMBOL_SIZE)
|
||||
val digits = IntArray(totalDigits)
|
||||
var x = value
|
||||
for (i in 0..totalDigits - 1) {
|
||||
digits[totalDigits - 1 - i] = x % 10
|
||||
x = x / 10
|
||||
}
|
||||
for (i in 0..totalDigits - 1) {
|
||||
copyRect(srcX = digits[i] * SYMBOL_SIZE,
|
||||
srcY = CELLS_HEIGHT + 3 * SYMBOL_SIZE,
|
||||
destX = labelDestX + SYMBOL_SIZE + i * SYMBOL_SIZE,
|
||||
destY = labelDestY + SYMBOL_SIZE,
|
||||
width = SYMBOL_SIZE,
|
||||
height = SYMBOL_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawGamePad() {
|
||||
if (gamePadButtons == null) return
|
||||
SDL_SetRenderDrawColor(renderer, 127, 127, 127, SDL_ALPHA_OPAQUE.toUByte())
|
||||
fillRect(gamePadButtons.leftRect)
|
||||
fillRect(gamePadButtons.downRect)
|
||||
fillRect(gamePadButtons.dropRect)
|
||||
fillRect(gamePadButtons.rightRect)
|
||||
fillRect(gamePadButtons.rotateRect)
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE.toUByte())
|
||||
}
|
||||
|
||||
private fun fillRect(rect: SDL_Rect) {
|
||||
memScoped {
|
||||
val stretchedRect = alloc<SDL_Rect>()
|
||||
stretchedRect.w = stretch(rect.w)
|
||||
stretchedRect.h = stretch(rect.h)
|
||||
stretchedRect.x = stretch(rect.x)
|
||||
stretchedRect.y = stretch(rect.y)
|
||||
SDL_RenderFillRect(renderer, stretchedRect.ptr.reinterpret())
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyRect(srcX: Int, srcY: Int, destX: Int, destY: Int, width: Int, height: Int) {
|
||||
memScoped {
|
||||
val srcRect = alloc<SDL_Rect>()
|
||||
val destRect = alloc<SDL_Rect>()
|
||||
srcRect.w = width
|
||||
srcRect.h = height
|
||||
srcRect.x = srcX
|
||||
srcRect.y = srcY
|
||||
destRect.w = stretch(width)
|
||||
destRect.h = stretch(height)
|
||||
destRect.x = stretch(destX)
|
||||
destRect.y = stretch(destY)
|
||||
SDL_RenderCopy(renderer, texture, srcRect.ptr.reinterpret(), destRect.ptr.reinterpret())
|
||||
}
|
||||
}
|
||||
|
||||
override fun readCommands(): List<UserCommand> {
|
||||
val commands = mutableListOf<UserCommand>()
|
||||
memScoped {
|
||||
val event = alloc<SDL_Event>()
|
||||
while (SDL_PollEvent(event.ptr.reinterpret()) != 0) {
|
||||
val eventType = event.type
|
||||
when (eventType) {
|
||||
SDL_QUIT -> commands.add(UserCommand.EXIT)
|
||||
SDL_KEYDOWN -> {
|
||||
val keyboardEvent = event.ptr.reinterpret<SDL_KeyboardEvent>().pointed
|
||||
when (keyboardEvent.keysym.scancode) {
|
||||
SDL_SCANCODE_LEFT -> commands.add(UserCommand.LEFT)
|
||||
SDL_SCANCODE_RIGHT -> commands.add(UserCommand.RIGHT)
|
||||
SDL_SCANCODE_DOWN -> commands.add(UserCommand.DOWN)
|
||||
SDL_SCANCODE_Z, SDL_SCANCODE_SPACE -> commands.add(UserCommand.ROTATE)
|
||||
SDL_SCANCODE_UP -> commands.add(UserCommand.DROP)
|
||||
SDL_SCANCODE_ESCAPE -> commands.add(UserCommand.EXIT)
|
||||
}
|
||||
}
|
||||
SDL_MOUSEBUTTONDOWN -> if (gamePadButtons != null) {
|
||||
val mouseEvent = event.ptr.reinterpret<SDL_MouseButtonEvent>().pointed
|
||||
val x = mouseEvent.x
|
||||
val y = mouseEvent.y
|
||||
val command = gamePadButtons.getCommandAt(x, y)
|
||||
if (command != null)
|
||||
commands.add(command)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return commands
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
SDL_DestroyTexture(texture)
|
||||
SDL_DestroyRenderer(renderer)
|
||||
SDL_DestroyWindow(window)
|
||||
SDL_Quit()
|
||||
gamePadButtons?.destroy()
|
||||
}
|
||||
}
|
||||
@@ -1,491 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.tetris
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.*
|
||||
|
||||
typealias Field = Array<ByteArray>
|
||||
|
||||
enum class Move {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
DOWN,
|
||||
ROTATE
|
||||
}
|
||||
|
||||
enum class PlacementResult(val linesCleared: Int = 0, val bonus: Int = 0) {
|
||||
NOTHING,
|
||||
GAMEOVER,
|
||||
// For values of bonuses see https://tetris.wiki/Scoring
|
||||
SINGLE(1, 40),
|
||||
DOUBLE(2, 100),
|
||||
TRIPLE(3, 300),
|
||||
TETRIS(4, 1200)
|
||||
}
|
||||
|
||||
const val EMPTY: Byte = 0
|
||||
const val CELL1: Byte = 1
|
||||
const val CELL2: Byte = 2
|
||||
const val CELL3: Byte = 3
|
||||
const val BRICK: Byte = -1
|
||||
|
||||
class Point(var x: Int, var y: Int)
|
||||
|
||||
operator fun Point.plus(other: Point): Point {
|
||||
return Point(x + other.x, y + other.y)
|
||||
}
|
||||
|
||||
class PiecePosition(piece: Piece, private val origin: Point) {
|
||||
private var p = piece.origin
|
||||
val x get() = p.x + origin.x
|
||||
val y get() = p.y + origin.y
|
||||
|
||||
var state: Int get private set
|
||||
val numberOfStates = piece.numberOfStates
|
||||
|
||||
init {
|
||||
state = 0
|
||||
}
|
||||
|
||||
fun makeMove(move: Move) {
|
||||
when (move) {
|
||||
Move.LEFT -> --p.y
|
||||
Move.RIGHT -> ++p.y
|
||||
Move.DOWN -> ++p.x
|
||||
Move.ROTATE -> state = (state + 1) % numberOfStates
|
||||
}
|
||||
}
|
||||
|
||||
fun unMakeMove(move: Move) {
|
||||
when (move) {
|
||||
Move.LEFT -> ++p.y
|
||||
Move.RIGHT -> --p.y
|
||||
Move.DOWN -> --p.x
|
||||
Move.ROTATE -> state = (state + numberOfStates - 1) % numberOfStates
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We use Nintendo Rotation System, right-handed version.
|
||||
* See https://tetris.wiki/Nintendo_Rotation_System
|
||||
*/
|
||||
enum class Piece(private val origin_: Point, private vararg val states: Field) {
|
||||
T(Point(-1, -2),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, EMPTY, EMPTY),
|
||||
byteArrayOf(CELL1, CELL1, CELL1),
|
||||
byteArrayOf(EMPTY, CELL1, EMPTY)),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, CELL1, EMPTY),
|
||||
byteArrayOf(CELL1, CELL1, EMPTY),
|
||||
byteArrayOf(EMPTY, CELL1, EMPTY)),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, CELL1, EMPTY),
|
||||
byteArrayOf(CELL1, CELL1, CELL1),
|
||||
byteArrayOf(EMPTY, EMPTY, EMPTY)),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, CELL1, EMPTY),
|
||||
byteArrayOf(EMPTY, CELL1, CELL1),
|
||||
byteArrayOf(EMPTY, CELL1, EMPTY))
|
||||
),
|
||||
J(Point(-1, -2),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, EMPTY, EMPTY),
|
||||
byteArrayOf(CELL2, CELL2, CELL2),
|
||||
byteArrayOf(EMPTY, EMPTY, CELL2)),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, CELL2, EMPTY),
|
||||
byteArrayOf(EMPTY, CELL2, EMPTY),
|
||||
byteArrayOf(CELL2, CELL2, EMPTY)),
|
||||
arrayOf(
|
||||
byteArrayOf(CELL2, EMPTY, EMPTY),
|
||||
byteArrayOf(CELL2, CELL2, CELL2),
|
||||
byteArrayOf(EMPTY, EMPTY, EMPTY)),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, CELL2, CELL2),
|
||||
byteArrayOf(EMPTY, CELL2, EMPTY),
|
||||
byteArrayOf(EMPTY, CELL2, EMPTY))
|
||||
),
|
||||
Z(Point(-1, -2),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, EMPTY, EMPTY),
|
||||
byteArrayOf(CELL3, CELL3, EMPTY),
|
||||
byteArrayOf(EMPTY, CELL3, CELL3)),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, EMPTY, CELL3),
|
||||
byteArrayOf(EMPTY, CELL3, CELL3),
|
||||
byteArrayOf(EMPTY, CELL3, EMPTY))
|
||||
),
|
||||
O(Point(0, -1),
|
||||
arrayOf(
|
||||
byteArrayOf(CELL1, CELL1),
|
||||
byteArrayOf(CELL1, CELL1))
|
||||
),
|
||||
S(Point(-1, -2),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, EMPTY, EMPTY),
|
||||
byteArrayOf(EMPTY, CELL2, CELL2),
|
||||
byteArrayOf(CELL2, CELL2, EMPTY)),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, CELL2, EMPTY),
|
||||
byteArrayOf(EMPTY, CELL2, CELL2),
|
||||
byteArrayOf(EMPTY, EMPTY, CELL2))
|
||||
),
|
||||
L(Point(-1, -2),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, EMPTY, EMPTY),
|
||||
byteArrayOf(CELL3, CELL3, CELL3),
|
||||
byteArrayOf(CELL3, EMPTY, EMPTY)),
|
||||
arrayOf(
|
||||
byteArrayOf(CELL3, CELL3, EMPTY),
|
||||
byteArrayOf(EMPTY, CELL3, EMPTY),
|
||||
byteArrayOf(EMPTY, CELL3, EMPTY)),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, EMPTY, CELL3),
|
||||
byteArrayOf(CELL3, CELL3, CELL3),
|
||||
byteArrayOf(EMPTY, EMPTY, EMPTY)),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, CELL3, EMPTY),
|
||||
byteArrayOf(EMPTY, CELL3, EMPTY),
|
||||
byteArrayOf(EMPTY, CELL3, CELL3))
|
||||
),
|
||||
I(Point(-2, -2),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, EMPTY, EMPTY, EMPTY),
|
||||
byteArrayOf(EMPTY, EMPTY, EMPTY, EMPTY),
|
||||
byteArrayOf(CELL1, CELL1, CELL1, CELL1),
|
||||
byteArrayOf(EMPTY, EMPTY, EMPTY, EMPTY)),
|
||||
arrayOf(
|
||||
byteArrayOf(EMPTY, EMPTY, CELL1, EMPTY),
|
||||
byteArrayOf(EMPTY, EMPTY, CELL1, EMPTY),
|
||||
byteArrayOf(EMPTY, EMPTY, CELL1, EMPTY),
|
||||
byteArrayOf(EMPTY, EMPTY, CELL1, EMPTY))
|
||||
);
|
||||
|
||||
val origin get() = Point(origin_.x, origin_.y)
|
||||
val numberOfStates: Int = states.size
|
||||
|
||||
fun canBePlaced(field: Field, position: PiecePosition): Boolean {
|
||||
val piece = states[position.state]
|
||||
val x = position.x
|
||||
val y = position.y
|
||||
for (i in piece.indices) {
|
||||
val pieceRow = piece[i]
|
||||
val boardRow = field[x + i]
|
||||
for (j in pieceRow.indices) {
|
||||
if (pieceRow[j] != EMPTY && boardRow[y + j] != EMPTY)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun place(field: Field, position: PiecePosition) {
|
||||
val piece = states[position.state]
|
||||
val x = position.x
|
||||
val y = position.y
|
||||
for (i in piece.indices) {
|
||||
val pieceRow = piece[i]
|
||||
for (j in pieceRow.indices) {
|
||||
if (pieceRow[j] != EMPTY) field[x + i][y + j] = pieceRow[j]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unPlace(field: Field, position: PiecePosition) {
|
||||
val piece = states[position.state]
|
||||
val x = position.x
|
||||
val y = position.y
|
||||
for (i in piece.indices) {
|
||||
val pieceRow = piece[i]
|
||||
for (j in pieceRow.indices) {
|
||||
if (pieceRow[j] != EMPTY) field[x + i][y + j] = EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface GameFieldVisualizer {
|
||||
fun drawCell(x: Int, y: Int, cell: Byte)
|
||||
fun drawNextPieceCell(x: Int, y: Int, cell: Byte)
|
||||
fun setInfo(linesCleared: Int, level: Int, score: Int, tetrises: Int)
|
||||
fun refresh()
|
||||
}
|
||||
|
||||
enum class UserCommand {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
DOWN,
|
||||
DROP,
|
||||
ROTATE,
|
||||
EXIT
|
||||
}
|
||||
|
||||
interface UserInput {
|
||||
fun readCommands(): List<UserCommand>
|
||||
}
|
||||
|
||||
class GameField(val width: Int, val height: Int, val visualizer: GameFieldVisualizer) {
|
||||
private val MARGIN = 4
|
||||
|
||||
private val field: Field
|
||||
private val origin: Point
|
||||
private val nextPieceField: Field
|
||||
|
||||
init {
|
||||
field = Array<ByteArray>(height + MARGIN * 2) { ByteArray(width + MARGIN * 2) }
|
||||
for (i in field.indices) {
|
||||
val row = field[i]
|
||||
for (j in row.indices) {
|
||||
if (i >= (MARGIN + height) // Bottom (field is flipped over).
|
||||
|| (j < MARGIN) // Left
|
||||
|| (j >= MARGIN + width)) // Right
|
||||
row[j] = BRICK
|
||||
}
|
||||
}
|
||||
// Coordinates are relative to the central axis and top of the field.
|
||||
origin = Point(MARGIN, MARGIN + (width + 1) / 2)
|
||||
nextPieceField = Array<ByteArray>(4) { ByteArray(4) }
|
||||
}
|
||||
|
||||
lateinit var currentPiece: Piece
|
||||
lateinit var nextPiece: Piece
|
||||
lateinit var currentPosition: PiecePosition
|
||||
|
||||
fun reset() {
|
||||
for (i in 0..height - 1)
|
||||
for (j in 0..width - 1)
|
||||
field[i + MARGIN][j + MARGIN] = 0
|
||||
srand(time(null).toUInt())
|
||||
nextPiece = getNextPiece(false)
|
||||
switchCurrentPiece()
|
||||
}
|
||||
|
||||
private fun randInt() = (rand() and 32767) or ((rand() and 32767) shl 15)
|
||||
|
||||
private fun getNextPiece(denyPrevious: Boolean): Piece {
|
||||
val pieces = Piece.values()
|
||||
if (!denyPrevious)
|
||||
return pieces[randInt() % pieces.size]
|
||||
while (true) {
|
||||
val nextPiece = pieces[randInt() % pieces.size]
|
||||
if (nextPiece != currentPiece) return nextPiece
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchCurrentPiece() {
|
||||
currentPiece = nextPiece
|
||||
nextPiece = getNextPiece(denyPrevious = true) // Forbid repeating the same piece for better distribution.
|
||||
currentPosition = PiecePosition(currentPiece, origin)
|
||||
}
|
||||
|
||||
fun makeMove(move: Move): Boolean {
|
||||
currentPosition.makeMove(move)
|
||||
if (currentPiece.canBePlaced(field, currentPosition))
|
||||
return true
|
||||
currentPosition.unMakeMove(move)
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Places current piece at its current location.
|
||||
*/
|
||||
fun place(): PlacementResult {
|
||||
currentPiece.place(field, currentPosition)
|
||||
val linesCleared = clearLines()
|
||||
if (isOutOfBorders()) return PlacementResult.GAMEOVER
|
||||
switchCurrentPiece()
|
||||
if (!currentPiece.canBePlaced(field, currentPosition))
|
||||
return PlacementResult.GAMEOVER
|
||||
when (linesCleared) {
|
||||
1 -> return PlacementResult.SINGLE
|
||||
2 -> return PlacementResult.DOUBLE
|
||||
3 -> return PlacementResult.TRIPLE
|
||||
4 -> return PlacementResult.TETRIS
|
||||
else -> return PlacementResult.NOTHING
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearLines(): Int {
|
||||
val clearedLines = mutableListOf<Int>()
|
||||
for (i in 0..height - 1) {
|
||||
val row = field[i + MARGIN]
|
||||
if ((0..width - 1).all { j -> row[j + MARGIN] != EMPTY }) {
|
||||
clearedLines.add(i + MARGIN)
|
||||
(0..width - 1).forEach { j -> row[j + MARGIN] = EMPTY }
|
||||
}
|
||||
}
|
||||
if (clearedLines.size == 0) return 0
|
||||
draw(false)
|
||||
visualizer.refresh()
|
||||
sleep(500)
|
||||
for (i in clearedLines) {
|
||||
for (k in i - 1 downTo 1)
|
||||
for (j in 0..width - 1)
|
||||
field[k + 1][j + MARGIN] = field[k][j + MARGIN]
|
||||
}
|
||||
draw(false)
|
||||
visualizer.refresh()
|
||||
return clearedLines.size
|
||||
}
|
||||
|
||||
private fun isOutOfBorders(): Boolean {
|
||||
for (i in 0..MARGIN - 1)
|
||||
for (j in 0..width - 1)
|
||||
if (field[i][j + MARGIN] != EMPTY)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
fun draw() {
|
||||
draw(true)
|
||||
drawNextPiece()
|
||||
}
|
||||
|
||||
private fun drawNextPiece() {
|
||||
for (i in 0..3)
|
||||
for (j in 0..3)
|
||||
nextPieceField[i][j] = 0
|
||||
nextPiece.place(nextPieceField, PiecePosition(nextPiece, Point(1, 2)))
|
||||
for (i in 0..3)
|
||||
for (j in 0..3)
|
||||
visualizer.drawNextPieceCell(i, j, nextPieceField[i][j])
|
||||
}
|
||||
|
||||
private fun draw(drawCurrentPiece: Boolean) {
|
||||
if (drawCurrentPiece)
|
||||
currentPiece.place(field, currentPosition)
|
||||
for (i in 0..height - 1)
|
||||
for (j in 0..width - 1)
|
||||
visualizer.drawCell(i, j, field[i + MARGIN][j + MARGIN])
|
||||
if (drawCurrentPiece)
|
||||
currentPiece.unPlace(field, currentPosition)
|
||||
}
|
||||
}
|
||||
|
||||
class Game(width: Int, height: Int, val visualizer: GameFieldVisualizer, val userInput: UserInput) {
|
||||
private val field = GameField(width, height, visualizer)
|
||||
|
||||
private var gameOver = true
|
||||
private var startLevel = 0
|
||||
private var leveledUp = false
|
||||
private var level = 0
|
||||
private var linesClearedAtCurrentLevel = 0
|
||||
private var linesCleared = 0
|
||||
private var tetrises = 0
|
||||
private var score = 0
|
||||
|
||||
/*
|
||||
* For speed constants and level up thresholds see https://tetris.wiki/Tetris_(NES,_Nintendo)
|
||||
*/
|
||||
private val speeds = intArrayOf(48, 43, 38, 33, 28, 23, 18, 13, 8, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2)
|
||||
private val levelUpThreshold
|
||||
get() =
|
||||
if (leveledUp) 10
|
||||
else minOf(startLevel * 10 + 10, maxOf(100, startLevel * 10 - 50))
|
||||
private val speed get() = if (level < 29) speeds[level] else 1
|
||||
|
||||
private var ticks = 0
|
||||
|
||||
fun startNewGame(level: Int) {
|
||||
gameOver = false
|
||||
startLevel = level
|
||||
leveledUp = false
|
||||
this.level = level
|
||||
linesClearedAtCurrentLevel = 0
|
||||
linesCleared = 0
|
||||
tetrises = 0
|
||||
score = 0
|
||||
ticks = 0
|
||||
field.reset()
|
||||
|
||||
visualizer.setInfo(linesCleared, level, score, tetrises)
|
||||
field.draw()
|
||||
visualizer.refresh()
|
||||
|
||||
mainLoop()
|
||||
}
|
||||
|
||||
private fun placePiece() {
|
||||
val placementResult = field.place()
|
||||
ticks = 0
|
||||
when (placementResult) {
|
||||
PlacementResult.NOTHING -> return
|
||||
PlacementResult.GAMEOVER -> {
|
||||
gameOver = true
|
||||
return
|
||||
}
|
||||
else -> {
|
||||
linesCleared += placementResult.linesCleared
|
||||
linesClearedAtCurrentLevel += placementResult.linesCleared
|
||||
score += placementResult.bonus * (level + 1)
|
||||
if (placementResult == PlacementResult.TETRIS)
|
||||
++tetrises
|
||||
val levelUpThreshold = levelUpThreshold
|
||||
if (linesClearedAtCurrentLevel >= levelUpThreshold) {
|
||||
++level
|
||||
linesClearedAtCurrentLevel -= levelUpThreshold
|
||||
leveledUp = true
|
||||
}
|
||||
|
||||
visualizer.setInfo(linesCleared, level, score, tetrises)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Number of additional gravity shifts before locking a piece landed on the ground.
|
||||
* This is needed in order to let user to move a piece to the left/right before locking.
|
||||
*/
|
||||
private val LOCK_DELAY = 1
|
||||
|
||||
private fun mainLoop() {
|
||||
var attemptsToLock = 0
|
||||
while (!gameOver) {
|
||||
sleep(1000 / 60) // Refresh rate - 60 frames per second.
|
||||
val commands = userInput.readCommands()
|
||||
for (cmd in commands) {
|
||||
val success: Boolean
|
||||
when (cmd) {
|
||||
UserCommand.EXIT -> return
|
||||
UserCommand.LEFT -> success = field.makeMove(Move.LEFT)
|
||||
UserCommand.RIGHT -> success = field.makeMove(Move.RIGHT)
|
||||
UserCommand.ROTATE -> success = field.makeMove(Move.ROTATE)
|
||||
UserCommand.DOWN -> {
|
||||
success = field.makeMove(Move.DOWN)
|
||||
if (!success) placePiece()
|
||||
}
|
||||
UserCommand.DROP -> {
|
||||
while (field.makeMove(Move.DOWN)) {
|
||||
}
|
||||
success = true
|
||||
placePiece()
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
field.draw()
|
||||
visualizer.refresh()
|
||||
}
|
||||
}
|
||||
++ticks
|
||||
if (ticks < speed) continue
|
||||
if (!field.makeMove(Move.DOWN)) {
|
||||
if (++attemptsToLock >= LOCK_DELAY) {
|
||||
placePiece()
|
||||
attemptsToLock = 0
|
||||
}
|
||||
}
|
||||
field.draw()
|
||||
visualizer.refresh()
|
||||
ticks -= speed
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.tetris
|
||||
|
||||
import kotlinx.cli.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val argParser = ArgParser("tetris", useDefaultHelpShortName = false)
|
||||
val level by argParser.option(ArgType.Int, shortName = "l", description = "Game level").default(Config.startLevel)
|
||||
val width by argParser.option(ArgType.Int, shortName = "w", description = "Width of the game field").default(Config.width)
|
||||
val height by argParser.option(ArgType.Int, shortName = "h", description = "Height of the game field").default(Config.height)
|
||||
argParser.parse(args)
|
||||
val visualizer = SDL_Visualizer(width, height)
|
||||
val game = Game(width, height, visualizer, visualizer)
|
||||
game.startNewGame(level)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>15G1212</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>tetris.kexe</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>tetris.kexe</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>tetris.kexe</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>14C89</string>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>10.2</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>14C89</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>iphoneos10.2</string>
|
||||
<key>DTXcode</key>
|
||||
<string>0821</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>8C1002</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>10.2</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CFBundleIcons</key>
|
||||
<dict>
|
||||
<key>CFBundlePrimaryIcon</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>Icon-60x2</string>
|
||||
<string>Icon-60</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,23 +0,0 @@
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 1,0,0,0
|
||||
PRODUCTVERSION 1,0,0,0
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "080904E4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "JetBrains LLC"
|
||||
VALUE "FileDescription", "Tetris Demo Game"
|
||||
VALUE "FileVersion", "1.0"
|
||||
VALUE "InternalName", "Tetris"
|
||||
VALUE "LegalCopyright", "©JetBrains"
|
||||
VALUE "OriginalFilename", "Tetris.exe"
|
||||
VALUE "ProductName", "Tetris for Windows"
|
||||
VALUE "ProductVersion", "1.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x809, 1252
|
||||
END
|
||||
END
|
||||
@@ -1,3 +0,0 @@
|
||||
width = 10
|
||||
height = 30
|
||||
startLevel = 0
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 110 KiB |
@@ -1,48 +0,0 @@
|
||||
# Torch demo
|
||||
|
||||
Trains a handwritten digit classifier using the [Torch](http://torch.ch) C backend.
|
||||
Like other Torch clients, most prominently [PyTorch](http://pytorch.org),
|
||||
this example is built on top of the
|
||||
[ATen C API](https://github.com/pytorch/pytorch/tree/master/aten),
|
||||
showing how a Torch client for Kotlin/Native could look like.
|
||||
|
||||
## Installation
|
||||
|
||||
To build [ATen (Torch for C)](https://github.com/pytorch/pytorch/tree/master/aten),
|
||||
make sure you have Python 2.X and pyyaml installed:
|
||||
|
||||
# macOS: if you don't have pip
|
||||
sudo easy_install pip
|
||||
# Linux: if you don't have pip
|
||||
apt-get -y install python-pip
|
||||
|
||||
# if you don't have pyyaml or typing
|
||||
sudo pip install pyyaml typing
|
||||
|
||||
Now
|
||||
|
||||
./downloadTorch.sh
|
||||
|
||||
will install it into `$HOME/.konan/third-party/torch` (if not yet done). One may override the location of
|
||||
`third-party/torch` by setting the `KONAN_DATA_DIR` environment variable.
|
||||
|
||||
To build use `../gradlew assemble`.
|
||||
|
||||
./downloadMNIST.sh
|
||||
|
||||
will download and unzip the [MNIST dataset](https://en.wikipedia.org/wiki/MNIST_database) of
|
||||
[70000 labeled handwritten digits](http://yann.lecun.com/exdb/mnist/) for training and testing a classifier (if not yet done).
|
||||
|
||||
Then run
|
||||
|
||||
../gradlew runReleaseExecutableTorch
|
||||
|
||||
Alternatively you can run the artifact directly through
|
||||
|
||||
./build/bin/torch/main/release/executable/torch.kexe
|
||||
|
||||
You may need to specify `LD_LIBRARY_PATH` or `DYLD_LIBRARY_PATH` environment variables
|
||||
to point to `$HOME/.konan/third-party/torch/lib` if the ATen dynamic library cannot be found.
|
||||
|
||||
Even on a CPU, training should only take some minutes,
|
||||
and you should observe a classification accuracy of about 95% on the test dataset.
|
||||
@@ -1,62 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
val kotlinNativeDataPath = System.getenv("KONAN_DATA_DIR")?.let { File(it) }
|
||||
?: File(System.getProperty("user.home")).resolve(".konan")
|
||||
|
||||
val torchHome = kotlinNativeDataPath.resolve("third-party/torch")
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
|
||||
// Create target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("torch")
|
||||
hostOs == "Linux" -> linuxX64("torch")
|
||||
// Windows is not supported
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.torch.main"
|
||||
linkerOpts("-L${torchHome.resolve("lib")}", "-lATen")
|
||||
runTask?.environment(
|
||||
"LD_LIBRARY_PATH" to torchHome.resolve("lib"),
|
||||
"DYLD_LIBRARY_PATH" to torchHome.resolve("lib")
|
||||
)
|
||||
}
|
||||
}
|
||||
compilations["main"].cinterops {
|
||||
val torch by creating {
|
||||
includeDirs(
|
||||
torchHome.resolve("include"),
|
||||
torchHome.resolve("include/TH")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val downloadTorch by tasks.creating(Exec::class) {
|
||||
workingDir = projectDir
|
||||
commandLine("./downloadTorch.sh")
|
||||
}
|
||||
|
||||
val torch: KotlinNativeTarget by kotlin.targets
|
||||
tasks[torch.compilations["main"].cinterops["torch"].interopProcessingTaskName].dependsOn(downloadTorch)
|
||||
|
||||
val downloadMNIST by tasks.creating(Exec::class) {
|
||||
workingDir = projectDir
|
||||
commandLine("./downloadMNIST.sh")
|
||||
}
|
||||
|
||||
NativeBuildType.values()
|
||||
.mapNotNull { torch.binaries.getExecutable(it).runTask }
|
||||
.forEach { runTask -> runTask.dependsOn(downloadMNIST) }
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# See http://yann.lecun.com/exdb/mnist/
|
||||
|
||||
MNIST_TARGET_DIRECTORY="`pwd`/build/3rd-party/MNIST"
|
||||
|
||||
echo "Downloading MNIST databases into $MNIST_TARGET_DIRECTORY ..."
|
||||
|
||||
mkdir -p $MNIST_TARGET_DIRECTORY
|
||||
cd $MNIST_TARGET_DIRECTORY
|
||||
|
||||
wget -nv -N http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
|
||||
wget -nv -N http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
|
||||
wget -nv -N http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
|
||||
wget -nv -N http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
|
||||
|
||||
gunzip -fk *.gz
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
KONAN_USER_DIR=${KONAN_DATA_DIR:-"$HOME/.konan"}
|
||||
TH_TARGET_DIRECTORY="$KONAN_USER_DIR/third-party/torch"
|
||||
NO_CUDA=true # set to false for GPU support
|
||||
|
||||
if [ ! -d $TH_TARGET_DIRECTORY/include/THNN ]; then
|
||||
echo "Installing Torch into $TH_TARGET_DIRECTORY ..."
|
||||
|
||||
mkdir -p build/3rd-party
|
||||
cd build/3rd-party
|
||||
|
||||
git clone https://github.com/pytorch/pytorch.git
|
||||
# Current pytorch master fails the build so we need to checkout a correct revision.
|
||||
cd pytorch && git checkout 310c3735b9eb97f30cee743b773e5bb054989edc^ && cd ../
|
||||
|
||||
mkdir build_torch
|
||||
cd build_torch
|
||||
|
||||
cmake -DNO_CUDA=$NO_CUDA ../pytorch/aten
|
||||
make
|
||||
make DESTDIR=$TH_TARGET_DIRECTORY install
|
||||
|
||||
cd $TH_TARGET_DIRECTORY
|
||||
|
||||
# remove 'usr/local' prefix produced by make:
|
||||
mv usr/local/* .
|
||||
rm -d usr/local usr
|
||||
|
||||
# hack to solve "fatal error: 'generic/THNN.h' file not found" when linking, -I$<DIR>/include/THNN did not work
|
||||
cp include/THNN/generic/THNN.h include/TH/generic/THNN.h
|
||||
fi
|
||||
@@ -1,2 +0,0 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.import.noCommonSourceSets=true
|
||||
@@ -1 +0,0 @@
|
||||
headers = TH/TH.h THNN/THNN.h
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.torch
|
||||
|
||||
fun Float.toRoundedString(digits: Int = 0): String {
|
||||
var factor = 1
|
||||
|
||||
for (i in 0 until digits) {
|
||||
factor *= 10
|
||||
}
|
||||
|
||||
return (kotlin.math.round(this * factor) / factor).toString()
|
||||
}
|
||||
|
||||
fun Float.toPercentageString(roundToDigits: Int = 1) = (this * 100).toRoundedString(roundToDigits)
|
||||
|
||||
fun List<Float>.maxIndex() = withIndex().maxByOrNull { it.value }!!.index
|
||||
|
||||
fun accuracy(predictionBatch: FloatMatrix, labelBatch: FloatMatrix): Float {
|
||||
val resultIndexes = predictionBatch.toList().map { it.maxIndex() }
|
||||
val labelBatchIndexes = labelBatch.toList().map { it.maxIndex() }
|
||||
return resultIndexes.zip(labelBatchIndexes).
|
||||
count { (result, label) -> result == label }.toFloat() / resultIndexes.size
|
||||
}
|
||||
|
||||
fun Backpropagatable<FloatMatrix, FloatMatrix>.trainClassifier(
|
||||
dataset: Dataset,
|
||||
lossByLabels: (FloatMatrix) -> Backpropagatable<FloatMatrix, FloatVector>,
|
||||
learningRateByProgress: (Float) -> Float = { 5f * kotlin.math.exp(-it * 3) },
|
||||
batchSize: Int = 64,
|
||||
iterations: Int = 500) {
|
||||
|
||||
for (i in 0 until iterations) {
|
||||
disposeScoped {
|
||||
val (inputBatch, labelBatch) = dataset.sampleBatch(batchSize)
|
||||
val errorNetwork = this@trainClassifier before lossByLabels(labelBatch)
|
||||
val forwardResults = use { errorNetwork.forwardPass(inputBatch) }
|
||||
val accuracy = accuracy(forwardResults.hidden, labelBatch)
|
||||
val progress = i.toFloat() / iterations
|
||||
val learningRate = learningRateByProgress(progress)
|
||||
val backpropResults = use { forwardResults.backpropagate(outputGradient = tensor(learningRate)) }
|
||||
val crossEntropy = forwardResults.output[0]
|
||||
backpropResults.descend()
|
||||
println("Iteration ${i + 1}/$iterations: " +
|
||||
"${accuracy.toPercentageString()}% training batch accuracy, " +
|
||||
"cross entropy loss = ${crossEntropy.toRoundedString(4)}, " +
|
||||
"learning rate = ${learningRate.toRoundedString(4)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Backpropagatable<FloatMatrix, FloatMatrix>.testClassifier(dataset: Dataset, batchSize: Int = 100): Float {
|
||||
val testBatches = dataset.testBatches(batchSize)
|
||||
return testBatches.withIndex().map { (i, batchPair) ->
|
||||
val (inputBatch, outputBatch) = batchPair
|
||||
val accuracy = accuracy(this.forwardPass(inputBatch).output, outputBatch)
|
||||
println("Test batch ${i + 1}/${testBatches.size}: ${accuracy.toPercentageString()}% accuracy")
|
||||
accuracy * inputBatch.shape[0]
|
||||
}.sum() / dataset.inputs.size
|
||||
}
|
||||
|
||||
fun randomInit(size: Int) = random(-.01, .01, size)
|
||||
fun randomInit(size0: Int, size1: Int) = random(-.1, .1, size0, size1)
|
||||
|
||||
fun linear(inputSize: Int, outputSize: Int) = Linear(randomInit(outputSize, inputSize), randomInit(outputSize))
|
||||
fun twoLayerClassifier(dataset: Dataset, hiddenSize: Int = 64) =
|
||||
linear(dataset.inputs[0].size, hiddenSize) before Relu before
|
||||
linear(hiddenSize, dataset.labels[0].size) before Softmax
|
||||
|
||||
fun main() {
|
||||
val trainingDataset = MNIST.labeledTrainingImages()
|
||||
val predictionNetwork = twoLayerClassifier(trainingDataset)
|
||||
predictionNetwork.trainClassifier(trainingDataset, lossByLabels = { CrossEntropyLoss(labels = it) })
|
||||
|
||||
val testDataset = MNIST.labeledTestImages()
|
||||
val averageAccuracy = predictionNetwork.testClassifier(testDataset)
|
||||
println("Accuracy on the test set: ${averageAccuracy.toPercentageString()}")
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.torch
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.posix.*
|
||||
|
||||
data class Dataset(val inputs: List<FloatArray>, val labels: List<FloatArray>) {
|
||||
fun batch(indices: List<Int>): Pair<FloatMatrix, FloatMatrix> {
|
||||
val inputBatch = tensor(*(indices.map { inputs[it].toTypedArray() }.toTypedArray()))
|
||||
val labelBatch = tensor(*(indices.map { labels[it].toTypedArray() }.toTypedArray()))
|
||||
return inputBatch to labelBatch
|
||||
}
|
||||
|
||||
fun sampleBatch(batchSize: Int) = batch((0 until batchSize).map { randomInt(inputs.size) })
|
||||
private fun batchAt(batchIndex: Int, batchSize: Int) =
|
||||
batch((0 until inputs.size).drop(batchSize + batchIndex).take(batchSize))
|
||||
|
||||
fun testBatches(batchSize: Int) = (0 until inputs.size / batchSize).map { batchAt(it, batchSize = batchSize) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the MNIST labeled handwritten digit dataset, described at http://yann.lecun.com/exdb/mnist/
|
||||
*/
|
||||
object MNIST {
|
||||
private fun readFileData(fileName: String) = memScoped {
|
||||
val path = "build/3rd-party/MNIST/$fileName"
|
||||
fun fail(): Nothing = throw Error("Cannot read input file $path")
|
||||
|
||||
val size = alloc<stat>().also { if (stat(path, it.ptr) != 0) fail() }.st_size.toInt()
|
||||
|
||||
println("Reading $size bytes from $path...")
|
||||
|
||||
val file = fopen(path, "rb") ?: fail()
|
||||
try {
|
||||
ByteArray(size).also { fread(it.refTo(0), 1, size.convert(), file) }
|
||||
} finally {
|
||||
fclose(file)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Byte.reinterpretAsUnsigned() = this.toInt().let { it + if (it < 0) 256 else 0 }
|
||||
|
||||
private fun unsignedBytesToInt(bytes: List<Byte>) =
|
||||
bytes.withIndex().map { (i, value) -> value.reinterpretAsUnsigned().shl(8 * (3 - i)) }.sum()
|
||||
|
||||
private val intSize = 4
|
||||
private fun ByteArray.getIntAt(index: Int) =
|
||||
unsignedBytesToInt((index until (index + intSize)).map { this[it] })
|
||||
|
||||
private val imageLength = 28
|
||||
private val imageSize = imageLength * imageLength
|
||||
|
||||
private fun ByteArray.getImageAt(index: Int) =
|
||||
FloatArray(imageSize) { this[index + it].reinterpretAsUnsigned().toFloat() / 255 }
|
||||
|
||||
private fun oneHot(size: Int, index: Int) = FloatArray(size) { if (it == index) 1f else 0f }
|
||||
|
||||
private fun readLabels(fileName: String, totalLabels: Int = 10): List<FloatArray> {
|
||||
val data = readFileData(fileName)
|
||||
val check = data.getIntAt(0)
|
||||
val expectedCheck = 2049
|
||||
if (check != 2049) throw Error("File should start with int $expectedCheck, but was $check.")
|
||||
|
||||
val count = data.getIntAt(4)
|
||||
|
||||
val offset = 8
|
||||
|
||||
if (count + offset != data.size) throw Error("Unexpected file size: ${data.size}.")
|
||||
|
||||
return (0 until count).map { oneHot(totalLabels, index = data[offset + it].reinterpretAsUnsigned()) }
|
||||
}
|
||||
|
||||
private fun readImages(fileName: String): List<FloatArray> {
|
||||
val data = readFileData(fileName)
|
||||
val check = data.getIntAt(0)
|
||||
val expectedCheck = 2051
|
||||
if (check != expectedCheck) throw Error("File should start with int $expectedCheck, but was $check.")
|
||||
|
||||
val count = data.getIntAt(4)
|
||||
val width = data.getIntAt(8)
|
||||
val height = data.getIntAt(12)
|
||||
|
||||
val offset = 16
|
||||
|
||||
if (width != imageLength) throw Error()
|
||||
if (height != imageLength) throw Error()
|
||||
|
||||
if (count * imageSize + offset != data.size) throw Error("Unexpected file size: ${data.size}.")
|
||||
|
||||
return (0 until count).map { data.getImageAt(offset + imageSize * it) }
|
||||
}
|
||||
|
||||
fun labeledTrainingImages() = Dataset(
|
||||
inputs = readImages("train-images-idx3-ubyte"),
|
||||
labels = readLabels("train-labels-idx1-ubyte"))
|
||||
|
||||
fun labeledTestImages() = Dataset(
|
||||
inputs = readImages("t10k-images-idx3-ubyte"),
|
||||
labels = readLabels("t10k-labels-idx1-ubyte"))
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.torch
|
||||
|
||||
/**
|
||||
* NOTE: resource management in this sample suffers from resource leaks
|
||||
* and double-free bugs (see workaround in [FloatTensor.dispose]).
|
||||
*
|
||||
* This might mean that the entire approach for resource management in the sample is faulty.
|
||||
* Please take this into account when considering reusing the same approach in your project.
|
||||
*
|
||||
* TODO: rework resource management.
|
||||
*/
|
||||
interface Disposable {
|
||||
fun dispose()
|
||||
}
|
||||
|
||||
open class DisposableContainer(private val disposables: MutableList<Disposable> = ArrayList()) : Disposable {
|
||||
/**
|
||||
* Creates the object and schedules its disposal for the end of the scope.
|
||||
*/
|
||||
fun <T : Disposable> use(create: () -> T) = create().also { disposables.add(it) }
|
||||
|
||||
override fun dispose() {
|
||||
for (disposable in disposables) {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> disposeScoped(action: DisposableContainer.() -> T): T {
|
||||
val scope = DisposableContainer()
|
||||
|
||||
try {
|
||||
return scope.action()
|
||||
} finally {
|
||||
scope.dispose()
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.torch
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import torch.*
|
||||
|
||||
// Defines network modules with the ability for backpropagation using both TH.h and THNN.h from the ATen library
|
||||
|
||||
abstract class Backpropagatable<Input : Disposable, Output : Disposable> {
|
||||
abstract inner class ForwardResults(val input: Input) : DisposableContainer() {
|
||||
init {
|
||||
use { input }
|
||||
}
|
||||
|
||||
abstract val output: Output
|
||||
abstract fun backpropagate(outputGradient: Output): BackpropagationResults
|
||||
}
|
||||
|
||||
abstract inner class BackpropagationResults(
|
||||
val input: Input,
|
||||
val output: Output,
|
||||
val outputGradient: Output
|
||||
) : DisposableContainer() {
|
||||
init {
|
||||
use { input }
|
||||
use { output }
|
||||
use { outputGradient }
|
||||
}
|
||||
|
||||
abstract val inputGradient: Input
|
||||
abstract fun descend()
|
||||
}
|
||||
|
||||
abstract fun forwardPass(input: Input): ForwardResults
|
||||
}
|
||||
|
||||
abstract class Module<Input : Disposable, Output : Disposable, Parameters> : Backpropagatable<Input, Output>() {
|
||||
abstract var parameters: Parameters
|
||||
abstract fun parametersToList(parameters: Parameters): List<FloatTensor>
|
||||
abstract fun parametersFromList(list: List<FloatTensor>): Parameters
|
||||
private val parameterList get() = parametersToList(parameters)
|
||||
|
||||
abstract operator fun invoke(input: Input): Output
|
||||
abstract fun inputGradient(input: Input, outputGradient: Output, output: Output): Input
|
||||
abstract fun parameterGradient(input: Input, outputGradient: Output, inputGradient: Input): Parameters
|
||||
|
||||
override fun forwardPass(input: Input) = object : ForwardResults(input) {
|
||||
override val output = use { this@Module(input) }
|
||||
override fun backpropagate(outputGradient: Output) =
|
||||
object : Backpropagatable<Input, Output>.BackpropagationResults(input, output, outputGradient) {
|
||||
override val inputGradient = use { this@Module.inputGradient(input, outputGradient, output) }
|
||||
val parameterGradient = this@Module.parameterGradient(input,
|
||||
outputGradient = outputGradient, inputGradient = inputGradient)
|
||||
|
||||
override fun descend() = this@Module.descend(parameterGradient)
|
||||
}
|
||||
}
|
||||
|
||||
open fun descend(parameterGradient: Parameters) {
|
||||
parameters = parametersFromList(parameterList.zip(
|
||||
parametersToList(parameterGradient)) { parameter, gradient -> parameter - gradient })
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ParameterFreeModule<Input : Disposable, Output : Disposable> : Module<Input, Output, Unit>() {
|
||||
override var parameters = Unit
|
||||
override fun parametersToList(parameters: Unit) = emptyList<FloatTensor>()
|
||||
override fun parametersFromList(list: List<FloatTensor>) = Unit
|
||||
override fun parameterGradient(input: Input, outputGradient: Output, inputGradient: Input) = Unit
|
||||
override fun descend(parameterGradient: Unit) {}
|
||||
}
|
||||
|
||||
class Chain<Input : Disposable, Hidden : Disposable, Output : Disposable>(
|
||||
val module1: Backpropagatable<Input, Hidden>,
|
||||
val module2: Backpropagatable<Hidden, Output>
|
||||
) : Backpropagatable<Input, Output>() {
|
||||
override fun forwardPass(input: Input) = ChainForwardResults(input)
|
||||
|
||||
inner class ChainForwardResults(input: Input) : ForwardResults(input) {
|
||||
val result1 = use { module1.forwardPass(input) }
|
||||
val hidden = result1.output
|
||||
val result2 = use { module2.forwardPass(result1.output) }
|
||||
override val output = result2.output
|
||||
override fun backpropagate(outputGradient: Output) =
|
||||
object : Backpropagatable<Input, Output>.BackpropagationResults(input, output, outputGradient) {
|
||||
val backpropResults2 = use { result2.backpropagate(outputGradient) }
|
||||
val hiddenGradient = backpropResults2.inputGradient
|
||||
val backpropResults1 = use { result1.backpropagate(hiddenGradient) }
|
||||
|
||||
override val inputGradient = backpropResults1.inputGradient
|
||||
|
||||
override fun descend() {
|
||||
backpropResults1.descend()
|
||||
backpropResults2.descend()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() = "$module1 before $module2"
|
||||
}
|
||||
|
||||
infix fun <Input : Disposable, Hidden : Disposable, Output : Disposable> Backpropagatable<Input, Hidden>.before(
|
||||
other: Backpropagatable<Hidden, Output>) = Chain(this, other)
|
||||
|
||||
object Abs : ParameterFreeModule<FloatMatrix, FloatMatrix>() {
|
||||
override operator fun invoke(input: FloatMatrix) = initializedTensor(input.shape[0], input.shape[1]) {
|
||||
THNN_FloatAbs_updateOutput(cValuesOf<FloatVar>(), input.raw, it.raw)
|
||||
}
|
||||
|
||||
override fun inputGradient(input: FloatMatrix, outputGradient: FloatMatrix, output: FloatMatrix) =
|
||||
initializedTensor(input.shape[0], input.shape[1]) {
|
||||
THNN_FloatAbs_updateGradInput(null, input.raw, outputGradient.raw, it.raw)
|
||||
}
|
||||
}
|
||||
|
||||
object Relu : ParameterFreeModule<FloatMatrix, FloatMatrix>() {
|
||||
override operator fun invoke(input: FloatMatrix) = initializedTensor(input.shape[0], input.shape[1]) {
|
||||
THNN_FloatLeakyReLU_updateOutput(null, input.raw, it.raw, 0.0, false)
|
||||
}
|
||||
|
||||
override fun inputGradient(input: FloatMatrix, outputGradient: FloatMatrix, output: FloatMatrix) =
|
||||
initializedTensor(input.shape[0], input.shape[1]) {
|
||||
THNN_FloatLeakyReLU_updateGradInput(null, input.raw, outputGradient.raw, it.raw, 0.0, false)
|
||||
}
|
||||
}
|
||||
|
||||
object Softmax : ParameterFreeModule<FloatMatrix, FloatMatrix>() {
|
||||
override operator fun invoke(input: FloatMatrix) = initializedTensor(input.shape[0], input.shape[1]) {
|
||||
THNN_FloatSoftMax_updateOutput(null, input.raw, it.raw, 1)
|
||||
}
|
||||
|
||||
override fun inputGradient(input: FloatMatrix, outputGradient: FloatMatrix, output: FloatMatrix) =
|
||||
initializedTensor(input.shape[0], input.shape[1]) {
|
||||
THNN_FloatSoftMax_updateGradInput(null, input.raw, outputGradient.raw, it.raw, output.raw, 1)
|
||||
}
|
||||
}
|
||||
|
||||
class MeanSquaredError(val labels: FloatMatrix) : ParameterFreeModule<FloatMatrix, FloatVector>() {
|
||||
override operator fun invoke(input: FloatMatrix) = initializedTensor(1) {
|
||||
THNN_FloatMSECriterion_updateOutput(null, input.raw, labels.raw, it.raw,
|
||||
sizeAverage = true, reduce = true)
|
||||
}
|
||||
|
||||
override fun inputGradient(
|
||||
input: FloatMatrix,
|
||||
outputGradient: FloatVector,
|
||||
output: FloatVector
|
||||
) = initializedTensor(input.shape[0], input.shape[1]) {
|
||||
THNN_FloatMSECriterion_updateGradInput(null, input.raw, labels.raw,
|
||||
outputGradient.raw, it.raw, sizeAverage = true, reduce = true)
|
||||
}
|
||||
}
|
||||
|
||||
class CrossEntropyLoss(val labels: FloatMatrix) : ParameterFreeModule<FloatMatrix, FloatVector>() {
|
||||
override operator fun invoke(input: FloatMatrix) = initializedTensor(1) {
|
||||
THNN_FloatBCECriterion_updateOutput(null, input.raw, labels.raw, it.raw,
|
||||
sizeAverage = true, reduce = true, weights = null)
|
||||
}
|
||||
|
||||
override fun inputGradient(input: FloatMatrix, outputGradient: FloatVector, output: FloatVector) =
|
||||
initializedTensor(input.shape[0], input.shape[1]) {
|
||||
THNN_FloatBCECriterion_updateGradInput(null, input.raw, labels.raw, outputGradient.raw,
|
||||
it.raw, sizeAverage = true, reduce = true, weights = null)
|
||||
}
|
||||
}
|
||||
|
||||
data class Linear(
|
||||
var weight: FloatMatrix,
|
||||
var bias: FloatVector) : Module<FloatMatrix, FloatMatrix, Pair<FloatMatrix, FloatVector>>() {
|
||||
val inputSize = weight.shape[1]
|
||||
val outputSize = weight.shape[0]
|
||||
val addBuffer = uninitializedTensor(outputSize)
|
||||
|
||||
override operator fun invoke(input: FloatMatrix) = initializedTensor(input.shape[0], outputSize) {
|
||||
THNN_FloatLinear_updateOutput(null, input.raw, it.raw, weight.raw, bias.raw, addBuffer.raw)
|
||||
}
|
||||
|
||||
override fun inputGradient(input: FloatMatrix, outputGradient: FloatMatrix, output: FloatMatrix) =
|
||||
initializedTensor(input.shape[0], inputSize) {
|
||||
THNN_FloatLinear_updateGradInput(null, input.raw, outputGradient.raw, it.raw, weight.raw)
|
||||
}
|
||||
|
||||
override fun parameterGradient(
|
||||
input: FloatMatrix,
|
||||
outputGradient: FloatMatrix,
|
||||
inputGradient: FloatMatrix
|
||||
): Pair<FloatMatrix, FloatVector> {
|
||||
val biasGradient = zeros(outputSize)
|
||||
val weightGradient = zeros(weight.shape[0], weight.shape[1]).also {
|
||||
THNN_FloatLinear_accGradParameters(null, input.raw, outputGradient.raw, inputGradient.raw, weight.raw,
|
||||
bias.raw, it.raw, biasGradient.raw, addBuffer.raw, 1.0)
|
||||
}
|
||||
|
||||
return weightGradient to biasGradient
|
||||
}
|
||||
|
||||
override var parameters: Pair<FloatMatrix, FloatVector>
|
||||
get() = weight to bias
|
||||
set(value) {
|
||||
weight = value.first
|
||||
bias = value.second
|
||||
}
|
||||
|
||||
override fun parametersToList(parameters: Pair<FloatMatrix, FloatVector>) =
|
||||
listOf(parameters.first, parameters.second)
|
||||
|
||||
override fun parametersFromList(list: List<FloatTensor>) = list.first().asMatrix() to list.last().asVector()
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.torch
|
||||
|
||||
// If you are curious you can also try one of these
|
||||
|
||||
private fun demonstrateTensors() {
|
||||
disposeScoped {
|
||||
val x = use { tensor(0f, 1f, 2f) }
|
||||
val y = use { tensor(0f, -1f, -2f) }
|
||||
val m = use {
|
||||
tensor(
|
||||
arrayOf(1f, -1f, 0f),
|
||||
arrayOf(0f, -1f, 0f),
|
||||
arrayOf(0f, 0f, -.5f))
|
||||
}
|
||||
|
||||
println("Hello, Torch!\nx = $x\ny = $y\n" +
|
||||
"|x| = ${x.abs()}\n|y| = ${y.abs()}\n" +
|
||||
"2x=${use { x * 2f }}\nx+y = ${use { x + y }}\nx-y = ${use { x - y }}\nxy = ${x * y}\n" +
|
||||
"m=\n${use { m }}\nm·y = ${use { m * y }}\nm+m =\n${use { m + m }}\nm·m =\n${use { m * m }}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun demonstrateModules() {
|
||||
val input = tensor(arrayOf(-1f))
|
||||
val abs = Abs(input)
|
||||
println("abs of $input is $abs, gradient is ${Abs.inputGradient(input, tensor(arrayOf(1f)), abs)}")
|
||||
val relu = Relu(input)
|
||||
println("relu of $input is $relu, gradient is ${Relu.inputGradient(input, tensor(arrayOf(1f)), relu)}")
|
||||
}
|
||||
|
||||
private fun demonstrateManualBackpropagationFor1LinearLayer(
|
||||
inputs: FloatMatrix = tensor(arrayOf(1f, -1f), arrayOf(1f, -1f)),
|
||||
labels: FloatMatrix = tensor(arrayOf(5f), arrayOf(5f)),
|
||||
learningRate: Float = .1f) {
|
||||
val linear = Linear(weight = randomInit(1, 2), bias = randomInit(1))
|
||||
val error = MeanSquaredError(labels)
|
||||
|
||||
for (i in 0 until 100) {
|
||||
disposeScoped {
|
||||
val output = use { linear(inputs) }
|
||||
val loss = use { error(output) }
|
||||
val outputGradient = use { error.inputGradient(output, tensor(learningRate), loss) }
|
||||
val inputGradient = use { linear.inputGradient(inputs, outputGradient, output) }
|
||||
val parameterGradient = linear.parameterGradient(inputs, outputGradient, inputGradient).
|
||||
also { use { it.first } }.also { use { it.second } }
|
||||
println("input: $inputs, \n" +
|
||||
"output: $output, \n" +
|
||||
"labels: $labels, \n" +
|
||||
"mean squared error: $loss, \n" +
|
||||
"output gradient: $outputGradient, \n" +
|
||||
"input gradient: $inputGradient, \n" +
|
||||
"parameter gradient: $parameterGradient")
|
||||
linear.weight -= parameterGradient.first
|
||||
linear.bias -= parameterGradient.second
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.torch
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import torch.*
|
||||
|
||||
// Defines tensor classes and operations using TH.h from the ATen library
|
||||
|
||||
abstract class FloatTensor(val raw: CPointer<THFloatTensor>) : Disposable {
|
||||
private val storage: CPointer<THFloatStorage> get() = raw.pointed.storage!!
|
||||
private val elements get() = storage.pointed
|
||||
private val data: CPointer<FloatVar> get() = elements.data!!
|
||||
private val size: CPointer<LongVar> get() = raw.pointed.size!!
|
||||
protected val nDimension: Int get() = raw.pointed.nDimension
|
||||
|
||||
val shape: List<Int> get() = (0 until nDimension).map { size[it].toInt() }
|
||||
|
||||
operator fun plus(other: FloatTensor) = initializedTensor(shape) {
|
||||
THFloatTensor_cadd(it.raw, raw, 1f, other.raw)
|
||||
}
|
||||
|
||||
operator fun minus(other: FloatTensor) = initializedTensor(shape) {
|
||||
THFloatTensor_cadd(it.raw, raw, -1f, other.raw)
|
||||
}
|
||||
|
||||
open operator fun times(factor: Float) = initializedTensor(shape) {
|
||||
THFloatTensor_mul(it.raw, raw, factor)
|
||||
}
|
||||
|
||||
fun sum() = THFloatTensor_sumall(raw)
|
||||
fun flatten() = (0 until elements.size).map { data[it] }.toTypedArray()
|
||||
|
||||
private var disposed = false
|
||||
override fun dispose() {
|
||||
if (disposed) return
|
||||
disposed = true
|
||||
|
||||
THFloatTensor_free(raw)
|
||||
}
|
||||
|
||||
fun asVector() = FloatVector(raw)
|
||||
fun asMatrix() = FloatMatrix(raw)
|
||||
inline fun <reified T : FloatTensor> asTensor(): T = when (T::class) {
|
||||
FloatVector::class -> asVector() as T
|
||||
FloatMatrix::class -> asMatrix() as T
|
||||
FloatTensor::class -> this as T
|
||||
else -> throw Error("Unexpected class ${T::class}")
|
||||
}
|
||||
|
||||
abstract override fun toString(): String
|
||||
}
|
||||
|
||||
class FloatVector(raw: CPointer<THFloatTensor>) : FloatTensor(raw) {
|
||||
init {
|
||||
if (super.nDimension != 1)
|
||||
throw Error("A vector must have exactly 1 dimension.")
|
||||
}
|
||||
|
||||
operator fun get(i: Int) = THFloatTensor_get1d(raw, i.signExtend())
|
||||
operator fun set(i: Int, value: Float) = THFloatTensor_set1d(raw, i.signExtend(), value)
|
||||
fun toArray() = (0 until shape[0]).map { i0 -> this[i0] }.toTypedArray()
|
||||
|
||||
operator fun plus(other: FloatVector) = super.plus(other).asVector()
|
||||
operator fun minus(other: FloatVector) = super.minus(other).asVector()
|
||||
override operator fun times(factor: Float) = super.times(factor).asVector()
|
||||
operator fun times(other: FloatVector) = THFloatTensor_dot(raw, other.raw)
|
||||
|
||||
fun abs() = kotlin.math.sqrt(this * this)
|
||||
|
||||
override fun toString() = "[${toArray().joinToString { it.toString() }}]"
|
||||
}
|
||||
|
||||
class FloatMatrix(raw: CPointer<THFloatTensor>) : FloatTensor(raw) {
|
||||
init {
|
||||
if (super.nDimension != 2)
|
||||
throw Error("A matrix must have exactly 2 dimensions.")
|
||||
}
|
||||
|
||||
fun getRow(i0: Int) = (0 until shape[1]).map { i1 -> this[i0, i1] }
|
||||
operator fun get(i0: Int, i1: Int) = THFloatTensor_get2d(raw, i0.signExtend(), i1.signExtend())
|
||||
operator fun set(i0: Int, i1: Int, value: Float) = THFloatTensor_set2d(raw, i0.signExtend(), i1.signExtend(), value)
|
||||
fun toList() = (0 until shape[0]).map { getRow(it) }
|
||||
|
||||
operator fun plus(other: FloatMatrix) = super.plus(other).asMatrix()
|
||||
operator fun minus(other: FloatMatrix) = super.minus(other).asMatrix()
|
||||
override operator fun times(factor: Float) = super.times(factor).asMatrix()
|
||||
|
||||
operator fun times(vector: FloatVector) = initializedTensor(shape[0]) {
|
||||
THFloatTensor_addmv(it.raw, 0f, it.raw, 1f, raw, vector.raw)
|
||||
}
|
||||
|
||||
operator fun times(matrix: FloatMatrix) = initializedTensor(shape[0], matrix.shape[1]) {
|
||||
THFloatTensor_addmm(it.raw, 0f, it.raw, 1f, raw, matrix.raw)
|
||||
}
|
||||
|
||||
override fun toString() = "[${toList().joinToString(",\n") { "[${it.joinToString { it.toString() }}]" }}]"
|
||||
}
|
||||
|
||||
fun uninitializedTensor(size: Int) =
|
||||
FloatVector(THFloatTensor_newWithSize1d(size.signExtend())!!)
|
||||
|
||||
fun uninitializedTensor(size0: Int, size1: Int) =
|
||||
FloatMatrix(THFloatTensor_newWithSize2d(size0.signExtend(), size1.signExtend())!!)
|
||||
|
||||
fun uninitializedTensor(shape: List<Int>) = when (shape.size) {
|
||||
1 -> uninitializedTensor(shape.single())
|
||||
2 -> uninitializedTensor(shape[0], shape[1])
|
||||
else -> throw Error("Tensors with ${shape.size} dimensions are not supported yet.")
|
||||
}
|
||||
|
||||
fun <T> initializedTensor(size: Int, initializer: (FloatVector) -> T) =
|
||||
uninitializedTensor(size).apply { initializer(this) }
|
||||
|
||||
fun <T> initializedTensor(size0: Int, size1: Int, initializer: (FloatMatrix) -> T) =
|
||||
uninitializedTensor(size0, size1).apply { initializer(this) }
|
||||
|
||||
fun <T> initializedTensor(shape: List<Int>, initializer: (FloatTensor) -> T) =
|
||||
uninitializedTensor(shape).apply { initializer(this) }
|
||||
|
||||
fun tensor(size: Int, initializer: (Int) -> Float) = initializedTensor(size) {
|
||||
for (i in 0 until size) {
|
||||
it[i] = initializer(i)
|
||||
}
|
||||
}
|
||||
|
||||
fun tensor(size0: Int, size1: Int, initializer: (Int, Int) -> Float) = initializedTensor(size0, size1) {
|
||||
for (i0 in 0 until size0) {
|
||||
for (i1 in 0 until size1) {
|
||||
it[i0, i1] = initializer(i0, i1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun tensor(vararg values: Float) = tensor(values.size) { values[it] }
|
||||
fun tensor(vararg values: Array<Float>) = tensor(values.size, values.first().size) { i0, i1 -> values[i0][i1] }
|
||||
|
||||
fun full(constant: Float, size: Int) = tensor(size) { constant }
|
||||
fun full(constant: Float, size0: Int, size1: Int) = tensor(size0, size1) { _, _ -> constant }
|
||||
fun full(constant: Float, shape: List<Int>) = when (shape.size) {
|
||||
1 -> full(constant, shape.single())
|
||||
2 -> full(constant, shape[0], shape[1])
|
||||
else -> throw Error("Tensors with ${shape.size} dimensions are not supported yet.")
|
||||
}
|
||||
|
||||
val randomGenerator = THGenerator_new()
|
||||
fun random(min: Float, max: Float) = THRandom_uniformFloat(randomGenerator, min, max)
|
||||
fun randomInt(count: Int, min: Int = 0) = random(min.toFloat(), count.toFloat()).toInt()
|
||||
fun random(min: Double, max: Double, size: Int) =
|
||||
initializedTensor(size) { THFloatTensor_uniform(it.raw, randomGenerator, min, max) }
|
||||
|
||||
fun random(min: Double, max: Double, size0: Int, size1: Int) =
|
||||
initializedTensor(size0, size1) { THFloatTensor_uniform(it.raw, randomGenerator, min, max) }
|
||||
|
||||
fun zeros(size: Int) = full(0f, size)
|
||||
fun zeros(size0: Int, size1: Int) = full(0f, size0, size1)
|
||||
fun zeros(shape: List<Int>) = full(0f, shape)
|
||||
|
||||
fun ones(size: Int) = full(1f, size)
|
||||
fun ones(size0: Int, size1: Int) = full(1f, size0, size1)
|
||||
fun ones(shape: List<Int>) = full(1f, shape)
|
||||
@@ -1,16 +0,0 @@
|
||||
# Additional files/dirs to unignore.
|
||||
|
||||
# Additional files/dirs to ignore.
|
||||
c_interop/
|
||||
*.kexe
|
||||
*.bak
|
||||
openweathermap_key.txt
|
||||
temp.txt
|
||||
temp.json
|
||||
.gradle/
|
||||
build/
|
||||
weather
|
||||
.konan/
|
||||
gradle/
|
||||
gradlew
|
||||
template/
|
||||
@@ -1,32 +0,0 @@
|
||||
FROM openjdk:8u171-jdk-stretch as builder
|
||||
WORKDIR /opt
|
||||
ENV kotlin_native_ver "0.7.1"
|
||||
ENV kotlin_native_home "/opt/kotlin-native-linux-$kotlin_native_ver"
|
||||
RUN wget "https://github.com/JetBrains/kotlin-native/releases/download/v$kotlin_native_ver/kotlin-native-linux-$kotlin_native_ver.tar.gz" \
|
||||
&& tar -xzf kotlin-native-linux-$kotlin_native_ver.tar.gz && rm kotlin-native-linux-$kotlin_native_ver.tar.gz \
|
||||
&& apt-get -qy update && apt-get install libcurl4-openssl-dev \
|
||||
&& mkdir -p /app/src/main/kotlin/org/example/weather_func \
|
||||
&& mkdir -p /app/lib/cJSON-1.7.7/include/cjson && mkdir -p /app/lib/cJSON-1.7.7/lib/x86_64-linux-gnu \
|
||||
&& mkdir -p /app/gradle/wrapper
|
||||
WORKDIR /app
|
||||
|
||||
COPY gradle/wrapper/gradle-wrapper.properties gradle/wrapper/gradle-wrapper.jar /app/gradle/wrapper/
|
||||
COPY gradlew openweathermap_key.txt /app/
|
||||
COPY .konan/ /root/.konan
|
||||
COPY *.def *.kts /app/
|
||||
COPY src/ /app/src
|
||||
COPY lib/cJSON-1.7.7/include/cjson/cJSON.h /app/lib/cJSON-1.7.7/include/cJSON.h
|
||||
COPY lib/cJSON-1.7.7/lib/x86_64-linux-gnu/libcjson.a /app/lib/cJSON-1.7.7/lib/libcjson.a
|
||||
RUN ./gradlew build && cp build/konan/bin/linux_x64/weather.kexe weather
|
||||
|
||||
FROM debian:stretch
|
||||
ADD https://github.com/openfaas/faas/releases/download/0.8.2/fwatchdog /usr/bin
|
||||
RUN apt-get update && apt-get -qy install libcurl4-openssl-dev \
|
||||
&& chmod +x /usr/bin/fwatchdog && mkdir -p /app
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/openweathermap_key.txt .
|
||||
COPY --from=builder /app/weather .
|
||||
RUN chmod +x weather
|
||||
ENV fprocess "./weather"
|
||||
HEALTHCHECK --interval=2s CMD [ -e /tmp/.lock ] || exit 1
|
||||
CMD ["fwatchdog"]
|
||||
@@ -1,24 +0,0 @@
|
||||
group = "org.example"
|
||||
version = "0.1-SNAPSHOT"
|
||||
|
||||
plugins {
|
||||
id("konan")
|
||||
}
|
||||
|
||||
konanArtifacts {
|
||||
interop("curl") {
|
||||
defFile("curl.def")
|
||||
}
|
||||
|
||||
interop("cjson") {
|
||||
defFile("cjson.def")
|
||||
}
|
||||
|
||||
program("weather") {
|
||||
entryPoint("org.example.weather_func.main")
|
||||
libraries {
|
||||
artifact("cjson")
|
||||
artifact("curl")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package = cjson
|
||||
headers = cJSON.h
|
||||
compilerOpts = -std=c89
|
||||
compilerOpts.linux = -I/app/lib/cJSON-1.7.7/include
|
||||
staticLibraries = libcjson.a
|
||||
libraryPaths = /app/lib/cJSON-1.7.7/lib
|
||||
@@ -1,5 +0,0 @@
|
||||
package = curl
|
||||
headers = curl.h
|
||||
linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lcurl
|
||||
compilerOpts = -std=c89
|
||||
compilerOpts.linux = -I/usr/include/x86_64-linux-gnu/curl
|
||||
@@ -1,19 +0,0 @@
|
||||
rootProject.name = "weather-function"
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
// This way we can map plugin to standard maven artifact
|
||||
// for maven repositories without plugin descriptor
|
||||
resolutionStrategy {
|
||||
eachPlugin {
|
||||
if (requested.id.id == "konan") {
|
||||
val kotlinNativeVer = "1.3.41"
|
||||
useModule("org.jetbrains.kotlin:kotlin-native-gradle-plugin:$kotlinNativeVer")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.example.weather_func
|
||||
|
||||
/** Models the current weather for a location. */
|
||||
internal data class Weather(
|
||||
val placeName: String,
|
||||
val countryCode: String,
|
||||
val windSpeed: Double = 0.0,
|
||||
val windDegrees: Int = 0,
|
||||
val temp: Int,
|
||||
val minTemp: Int,
|
||||
val maxTemp: Int,
|
||||
val humidity: Int,
|
||||
val conditions: Array<Pair<String, String>>
|
||||
)
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.example.weather_func
|
||||
|
||||
import org.example.weather_func.Event
|
||||
import platform.posix.size_t
|
||||
|
||||
import curl.curl_easy_setopt
|
||||
import curl.CURLOPT_URL
|
||||
import curl.CURLOPT_HEADERFUNCTION
|
||||
import curl.CURLOPT_HEADERDATA
|
||||
import curl.CURLOPT_WRITEFUNCTION
|
||||
import curl.CURLOPT_WRITEDATA
|
||||
import curl.curl_easy_cleanup
|
||||
import curl.curl_easy_init
|
||||
import curl.CURLE_OK
|
||||
import curl.curl_easy_strerror
|
||||
import curl.curl_easy_perform
|
||||
|
||||
import kotlinx.cinterop.staticCFunction
|
||||
import kotlinx.cinterop.COpaquePointer
|
||||
import kotlinx.cinterop.CPointer
|
||||
import kotlinx.cinterop.StableRef
|
||||
import kotlinx.cinterop.asStableRef
|
||||
import kotlinx.cinterop.ByteVar
|
||||
import kotlinx.cinterop.readBytes
|
||||
|
||||
/**
|
||||
* Provides basic HTTP client functionality through the libCurl library.
|
||||
*/
|
||||
internal class CUrl(val url: String) {
|
||||
private val stableRef = StableRef.create(this)
|
||||
private val curlObj = curl_easy_init()
|
||||
val header = Event<String>()
|
||||
val body = Event<String>()
|
||||
|
||||
init {
|
||||
val header = staticCFunction(::headerCallback)
|
||||
val writeData = staticCFunction(::writeCallback)
|
||||
|
||||
// Setup Curl.
|
||||
curl_easy_setopt(curlObj, CURLOPT_URL, url)
|
||||
curl_easy_setopt(curlObj, CURLOPT_HEADERFUNCTION, header)
|
||||
curl_easy_setopt(curlObj, CURLOPT_HEADERDATA, stableRef.asCPointer())
|
||||
curl_easy_setopt(curlObj, CURLOPT_WRITEFUNCTION, writeData)
|
||||
curl_easy_setopt(curlObj, CURLOPT_WRITEDATA, stableRef.asCPointer())
|
||||
}
|
||||
|
||||
fun fetch() {
|
||||
// Have Curl do a HTTP/HTTPS request and store the response (status).
|
||||
val response = curl_easy_perform(curlObj)
|
||||
val maxChars = 50
|
||||
// utfCharSize in bytes.
|
||||
val utfCharSize = 4
|
||||
// length in bytes.
|
||||
val length = utfCharSize * maxChars
|
||||
// Print the error message if the Curl status code isn't OK (CURLE_OK).
|
||||
if (response != CURLE_OK) println("Curl HTTP/S request failed: ${curl_easy_strerror(response)?.toKString(length)}")
|
||||
}
|
||||
|
||||
fun close() {
|
||||
curl_easy_cleanup(curlObj)
|
||||
stableRef.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
fun headerCallback(buffer: CPointer<ByteVar>?, size: size_t, totalItems: size_t, userData: COpaquePointer?): size_t {
|
||||
var responseSize = 0L
|
||||
if (buffer != null && userData != null) {
|
||||
val header = buffer.toKString((size * totalItems).toInt()).trim()
|
||||
val curlRef = userData.asStableRef<CUrl>().get()
|
||||
|
||||
curlRef.header(header)
|
||||
responseSize = size * totalItems
|
||||
}
|
||||
return responseSize
|
||||
}
|
||||
|
||||
fun writeCallback(buffer: CPointer<ByteVar>?, size: size_t, totalItems: size_t, userData: COpaquePointer?): size_t {
|
||||
var responseSize = 0L
|
||||
if (buffer != null && userData != null) {
|
||||
val data = buffer.toKString((size * totalItems).toInt()).trim()
|
||||
val curlRef = userData.asStableRef<CUrl>().get()
|
||||
|
||||
curlRef.body(data)
|
||||
responseSize = size * totalItems
|
||||
}
|
||||
return responseSize
|
||||
}
|
||||
|
||||
private fun CPointer<ByteVar>.toKString(length: Int) = readBytes(length).decodeToString()
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.example.weather_func
|
||||
|
||||
internal typealias EventHandler<T> = (T) -> Unit
|
||||
|
||||
/**
|
||||
* Covers event handling for the program.
|
||||
*/
|
||||
internal class Event<T : Any?> {
|
||||
private var handlers = emptyList<EventHandler<T>>()
|
||||
|
||||
fun subscribe(handler: EventHandler<T>) {
|
||||
handlers += handler
|
||||
}
|
||||
|
||||
fun unsubscribe(handler: EventHandler<T>) {
|
||||
handlers -= handler
|
||||
}
|
||||
|
||||
operator fun plusAssign(handler: EventHandler<T>) = subscribe(handler)
|
||||
operator fun minusAssign(handler: EventHandler<T>) = unsubscribe(handler)
|
||||
|
||||
operator fun invoke(value: T) {
|
||||
var exception: Throwable? = null
|
||||
for (handler in handlers) {
|
||||
try { handler(value) }
|
||||
catch (ex: Throwable) { exception = ex }
|
||||
}
|
||||
// If the exception isn't null then throw it.
|
||||
exception?.let { throw it }
|
||||
}
|
||||
}
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.example.weather_func
|
||||
|
||||
import cjson.cJSON_CreateObject as createJsonObject
|
||||
import cjson.cJSON_AddStringToObject as addStringToObject
|
||||
import cjson.cJSON_AddNumberToObject as addNumberToObject
|
||||
import cjson.cJSON_AddArrayToObject as addArrayToObject
|
||||
import cjson.cJSON_AddItemToArray as addItemToArray
|
||||
import cjson.cJSON_AddItemToObjectCS as addItemToObject
|
||||
|
||||
import cjson.cJSON_Delete as deleteObject
|
||||
import cjson.cJSON_Print as jsonString
|
||||
import cjson.cJSON_Parse as parseJson
|
||||
import cjson.cJSON_GetObjectItemCaseSensitive as jsonObjectItem
|
||||
import cjson.cJSON_IsString as jsonValueIsString
|
||||
|
||||
import cjson.cJSON_IsNumber as jsonValueIsNumber
|
||||
import cjson.cJSON_GetErrorPtr as getErrorPointer
|
||||
import cjson.cJSON_GetStringValue as jsonStringValue
|
||||
import cjson.cJSON_GetArraySize as jsonArraySize
|
||||
import cjson.cJSON_GetArrayItem as jsonArrayItem
|
||||
import cjson.cJSON
|
||||
|
||||
import kotlinx.cinterop.CPointer
|
||||
import kotlinx.cinterop.toKString
|
||||
import kotlinx.cinterop.pointed
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
private val trueValue = 1
|
||||
|
||||
internal fun createWeatherFromJson(jsonStr: String): Weather {
|
||||
val rootObj = parseJson(jsonStr)
|
||||
checkForError()
|
||||
val result = createWeather(
|
||||
placeName = jsonObjectItem(rootObj, "name").stringValue(),
|
||||
countryCode = extractCountryCode(rootObj),
|
||||
windInfo = extractWindInfo(rootObj),
|
||||
conditions = extractConditions(rootObj),
|
||||
tempInfo = extractTempInfo(rootObj),
|
||||
humidity = extractHumidity(rootObj)
|
||||
)
|
||||
if (rootObj != null) deleteObject(rootObj)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Weather object.
|
||||
* @param placeName Name of the place.
|
||||
* @param countryCode Unique country code.
|
||||
* @param windInfo A Pair with wind speed as 1st item, and wind degrees as 2nd item.
|
||||
* @param conditions A Array of Pair. Each Pair has name as 1st item, and desc as 2nd item.
|
||||
* @param humidity General humidity of the atmosphere.
|
||||
* @param tempInfo A Triple with temperature as 1st item, min temperature as 2nd item, and max temperature as 3rd item.
|
||||
* @return A instance of Weather.
|
||||
*/
|
||||
private fun createWeather(
|
||||
placeName: String,
|
||||
countryCode: String,
|
||||
windInfo: Pair<Double, Int>,
|
||||
conditions: Array<Pair<String, String>>,
|
||||
humidity: Int,
|
||||
tempInfo: Triple<Int, Int, Int>
|
||||
) = Weather(
|
||||
placeName = placeName,
|
||||
countryCode = countryCode,
|
||||
conditions = conditions,
|
||||
humidity = humidity,
|
||||
windSpeed = windInfo.first,
|
||||
windDegrees = windInfo.second,
|
||||
temp = tempInfo.first,
|
||||
minTemp = tempInfo.second,
|
||||
maxTemp = tempInfo.third
|
||||
)
|
||||
|
||||
private fun extractTempInfo(rootObj: CPointer<cJSON>?): Triple<Int, Int, Int> {
|
||||
val mainObj = jsonObjectItem(rootObj, "main")
|
||||
val temp = jsonObjectItem(mainObj, "temp").intValue()
|
||||
val minTemp = jsonObjectItem(mainObj, "temp_min").intValue()
|
||||
val maxTemp = jsonObjectItem(mainObj, "temp_max").intValue()
|
||||
|
||||
return Triple(temp, minTemp, maxTemp)
|
||||
}
|
||||
|
||||
private fun extractHumidity(rootObj: CPointer<cJSON>?): Int {
|
||||
val mainObj = jsonObjectItem(rootObj, "main")
|
||||
|
||||
return jsonObjectItem(mainObj, "humidity").intValue()
|
||||
}
|
||||
|
||||
private fun extractConditions(rootObj: CPointer<cJSON>?): Array<Pair<String, String>> {
|
||||
val tmp = mutableListOf<Pair<String, String>>()
|
||||
val weatherObj = jsonObjectItem(rootObj, "weather")
|
||||
|
||||
for (pos in 0..(jsonArraySize(weatherObj) - 1)) {
|
||||
val item = jsonArrayItem(weatherObj, pos)
|
||||
val name = jsonObjectItem(item, "main").stringValue()
|
||||
val desc = jsonObjectItem(item, "description").stringValue()
|
||||
|
||||
tmp += (name to desc)
|
||||
}
|
||||
return tmp.toTypedArray()
|
||||
}
|
||||
|
||||
private fun extractWindInfo(rootObj: CPointer<cJSON>?): Pair<Double, Int> {
|
||||
val windObj = jsonObjectItem(rootObj, "wind")
|
||||
val windSpeed = jsonObjectItem(windObj, "speed").doubleValue()
|
||||
val windDegrees = jsonObjectItem(windObj, "deg").intValue()
|
||||
|
||||
return (windSpeed to windDegrees)
|
||||
}
|
||||
|
||||
private fun extractCountryCode(rootObj: CPointer<cJSON>?): String {
|
||||
val sys = jsonObjectItem(rootObj, "sys")
|
||||
|
||||
return jsonObjectItem(sys, "country").stringValue()
|
||||
}
|
||||
|
||||
internal fun weatherToJsonString(weather: Weather) : String {
|
||||
val rootObj = createJsonObject()
|
||||
addStringToObject(rootObj, "placeName", weather.placeName)
|
||||
addStringToObject(rootObj, "countryCode", weather.countryCode)
|
||||
addNumberToObject(rootObj, "humidity", weather.humidity.toDouble())
|
||||
addTempInfoArray(rootObj, temp = weather.temp, minTemp = weather.minTemp, maxTemp = weather.maxTemp)
|
||||
addConditionsArray(rootObj, weather.conditions)
|
||||
val result = jsonString(rootObj)?.toKString() ?: ""
|
||||
deleteObject(rootObj)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun addTempInfoArray(rootObj: CPointer<cJSON>?, temp: Int, minTemp: Int, maxTemp: Int) {
|
||||
val tmpObj = createJsonObject()
|
||||
|
||||
addNumberToObject(tmpObj, "temp", temp.toDouble())
|
||||
addNumberToObject(tmpObj, "minTemp", minTemp.toDouble())
|
||||
addNumberToObject(tmpObj, "maxTemp", maxTemp.toDouble())
|
||||
addItemToObject(rootObj, "tempInfo", tmpObj)
|
||||
}
|
||||
|
||||
private fun addConditionsArray(rootObj: CPointer<cJSON>?, conditions: Array<Pair<String, String>>) {
|
||||
val tmpArr = addArrayToObject(rootObj, "conditions")
|
||||
|
||||
conditions.forEach { (name, desc) ->
|
||||
val tmpObj = createJsonObject()
|
||||
addStringToObject(tmpObj, "name", name)
|
||||
addStringToObject(tmpObj, "desc", desc)
|
||||
addItemToArray(tmpArr, tmpObj)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkForError() {
|
||||
val errorMsg = getErrorPointer()?.toKString() ?: ""
|
||||
|
||||
if (errorMsg.isNotEmpty()) {
|
||||
println("JSON error before: $errorMsg\n")
|
||||
exitProcess(-1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CPointer<cJSON>?.intValue() = this?.pointed?.valueint ?: 0
|
||||
|
||||
private fun CPointer<cJSON>?.doubleValue() = this?.pointed?.valuedouble ?: 0.0
|
||||
|
||||
private fun CPointer<cJSON>?.stringValue() = jsonStringValue(this)?.toKString() ?: ""
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.example.weather_func
|
||||
|
||||
import platform.posix.*
|
||||
import kotlinx.cinterop.allocArray
|
||||
import kotlinx.cinterop.ByteVar
|
||||
import kotlinx.cinterop.memScoped
|
||||
import kotlinx.cinterop.toKString
|
||||
import kotlinx.cinterop.refTo
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
private val API_KEY by lazy { fetchApiKey() }
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val input = readLine()
|
||||
val location = fetchLocationArg(input)
|
||||
val jsonFile = fetchJsonFileArg(input)
|
||||
|
||||
if (location.isNotEmpty()) {
|
||||
printFromWeatherService(location)
|
||||
} else if (jsonFile.isNotEmpty()) {
|
||||
checkFile(jsonFile)
|
||||
printJsonFile(jsonFile)
|
||||
} else {
|
||||
handleEmptyInput()
|
||||
}
|
||||
println("Exiting...")
|
||||
}
|
||||
|
||||
private fun handleEmptyInput() {
|
||||
println(
|
||||
"""
|
||||
| Weather - A OpenFaaS Serverless Function that outputs weather information.
|
||||
| Usage examples (pass one of the following as input to the function):
|
||||
| * Location (uses the Open Weather Map service), eg:
|
||||
| -l="christchurch,nz"
|
||||
| * JSON File, eg: -f="weather.json"
|
||||
""".trimMargin()
|
||||
)
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
private fun checkFile(file: String) {
|
||||
val error = -1
|
||||
if (access(file, F_OK) == error) {
|
||||
println("File $file doesn't exist!")
|
||||
exitProcess(error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchJson(jsonFile: String): String {
|
||||
var result = ""
|
||||
// Open the file using the fopen function and store the file handle.
|
||||
val file = fopen(jsonFile, "r")
|
||||
fseek(file, 0, SEEK_END)
|
||||
val fileSize = ftell(file)
|
||||
fseek(file, 0, SEEK_SET)
|
||||
|
||||
memScoped {
|
||||
val buffer = allocArray<ByteVar>(fileSize)
|
||||
// Read the entire file and store the contents into the buffer.
|
||||
fread(buffer, fileSize, 1, file)
|
||||
result = buffer.toKString()
|
||||
}
|
||||
// Close the file.
|
||||
fclose(file)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun printJsonFile(jsonFile: String) {
|
||||
println("Printing from JSON file ($jsonFile)...")
|
||||
val weather = createWeatherFromJson(fetchJson(jsonFile))
|
||||
println("Weather Object:\n$weather")
|
||||
println("Weather JSON:\n${weatherToJsonString(weather)}")
|
||||
}
|
||||
|
||||
private fun printFromWeatherService(location: String) {
|
||||
println("Fetching weather information (for $location)...")
|
||||
val curl = CUrl(createUrl(location)).apply {
|
||||
header += { if(it.startsWith("HTTP")) println("Response Status: $it") }
|
||||
body += { data ->
|
||||
val weather = createWeatherFromJson(data)
|
||||
println("Weather information:\n${weatherToJsonString(weather)}")
|
||||
}
|
||||
}
|
||||
curl.fetch()
|
||||
curl.close()
|
||||
}
|
||||
|
||||
private fun fetchJsonFileArg(input: String?): String {
|
||||
val flag = "-f"
|
||||
return if (input != null && input.startsWith("$flag=")) input.replace("$flag=", "").replace("\"", "")
|
||||
else ""
|
||||
}
|
||||
|
||||
private fun fetchLocationArg(input: String?): String {
|
||||
val flag = "-l"
|
||||
return if (input != null && input.startsWith("$flag=")) input.replace("$flag=", "").replace("\"", "")
|
||||
else ""
|
||||
}
|
||||
|
||||
private fun createUrl(location: String): String {
|
||||
val baseUrl = "http://api.openweathermap.org/data/2.5/weather"
|
||||
return "$baseUrl?q=$location&units=metric&appid=$API_KEY"
|
||||
}
|
||||
|
||||
private fun fetchApiKey(): String {
|
||||
var result = ""
|
||||
val maxChars = 50
|
||||
// utfCharSize in bytes.
|
||||
val utfCharSize = 4
|
||||
// Open the file using the fopen function and store the file handle.
|
||||
val file = fopen("openweathermap_key.txt", "r")
|
||||
val buffer = ByteArray(utfCharSize * maxChars)
|
||||
if (file != null) result = fgets(buffer.refTo(0), buffer.size, file)?.toKString()?.trim() ?: ""
|
||||
// Close the file.
|
||||
fclose(file)
|
||||
return result
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
# Weather Function Sample
|
||||
|
||||
This is a Serverless Function that fetches the current weather information from the Open Weather Map [service](https://openweathermap.org/current) via HTTP. Part of the Function's output includes HTTP response headers, and the HTTP response body. This sample is designed to be deployed on OpenFaaS via a Docker image, in conjunction with Docker Swarm.
|
||||
|
||||
|
||||
# Initial Setup
|
||||
|
||||
If the **~/.konan/cache** directory doesn't exist then you will need to compile a basic Linux amd64 program with konan before proceeding.
|
||||
|
||||
1. Clone the Kotlin Native Git repository: `git clone https://github.com/JetBrains/kotlin-native ~/repos/kotlin-native`
|
||||
2. Change working directory to the sample: `cd ~/repos/kotlin-native/samples/weather_function`
|
||||
3. Copy the Konan cache to the sample: `mkdir -p function/.konan || cp -R ~/.konan/cache function/.konan`
|
||||
4. Copy the Gradle Wrapper to the sample: `cp ../gradle function`
|
||||
5. Copy the **gradlew** file to the sample: `cp ../gradlew function`
|
||||
6. Create the **openweathermap_key.txt** file in the **function** directory: `touch function/openweathermap_key.txt`
|
||||
7. Append your [Open Weather Map API key](https://openweathermap.org/appid) to **function/openweathermap_key.txt**
|
||||
|
||||
|
||||
# Building Docker Image
|
||||
|
||||
It will be assumed that [Docker](https://store.docker.com/search?type=edition&offering=community), and [OpenFaaS](https://docs.openfaas.com/deployment/) is already installed along with the [OpenFaaS CLI](https://github.com/openfaas/faas-cli). Build the *weather* Docker image using the **fass-cli** tool: `faas-cli build -f weather.yml`
|
||||
|
||||
|
||||
# Usage
|
||||
|
||||
Make sure that you have completed the *Building Docker Image* section before proceeding.
|
||||
|
||||
1. Start OpenFaaS
|
||||
2. Deploy the **weather** image using the **faas-cli** tool: `faas-cli deploy --image weather --name weather`
|
||||
3. Invoke the **weather** function (including passing through the location argument) by running the following: `echo '-l="christchurch,nz"' | faas-cli invoke weather`
|
||||
|
||||
**Note:** The program (weather) can print weather information from a JSON file, eg: `./weather -f="current_weather.json"`. This functionality isn't available in the Serverless Function unless the file is generated in the **~/repos/kotlin-native/samples/weather_function/function** directory, the Dockerfile is updated to include the file in the Docker image, and the image is built (refer to the *Building Docker Image* section) before deploying the image (refer to the *Usage* section).
|
||||
@@ -1,9 +0,0 @@
|
||||
provider:
|
||||
name: faas
|
||||
gateway: http://127.0.0.1:8080
|
||||
|
||||
functions:
|
||||
function:
|
||||
lang: dockerfile
|
||||
handler: function
|
||||
image: weather
|
||||
@@ -1,23 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.8.2)
|
||||
|
||||
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
|
||||
project(NONE)
|
||||
|
||||
add_library(libsupcpp STATIC IMPORTED)
|
||||
|
||||
set_property(TARGET libsupcpp PROPERTY IMPORTED_LOCATION $ENV{GNUARMEMB_TOOLCHAIN_PATH}/arm-none-eabi/lib/thumb/libsupc++.a)
|
||||
|
||||
target_link_libraries(app PRIVATE libsupcpp)
|
||||
|
||||
set_property(TARGET app PROPERTY LINKER_LANGUAGE C)
|
||||
|
||||
target_sources(app PRIVATE build/program.o)
|
||||
|
||||
set_source_files_properties(program.o PROPERTIES EXTERNAL_OBJECT true GENERATED true)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT program.o
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_BINARY_DIR}/kotlin/program.o
|
||||
${CMAKE_BINARY_DIR}/program.o
|
||||
)
|
||||
@@ -1,55 +0,0 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set BOARD=stm32f4_disco
|
||||
set ZEPHYR_BASE=%userprofile%\zephyr
|
||||
set TOOLCHAIN=gcc-arm-none-eabi-7-2017-q4-major-win32
|
||||
|
||||
set DIR=%~dp0
|
||||
if "%KONAN_DATA_DIR%"=="" (set KONAN_DATA_DIR=%userprofile%\.konan)
|
||||
set KONAN_DEPS=%KONAN_DATA_DIR%/dependencies
|
||||
set GNU_ARM=%KONAN_DEPS%/%TOOLCHAIN%
|
||||
set ZEPHYR_TOOLCHAIN_VARIANT=gnuarmemb
|
||||
set GNUARMEMB_TOOLCHAIN_PATH=%GNU_ARM%
|
||||
|
||||
if defined KONAN_HOME (
|
||||
set "PATH=%KONAN_HOME%\bin;%PATH%"
|
||||
) else (
|
||||
set "PATH=%DIR%..\..\dist\bin;%DIR%..\..\bin;%PATH%"
|
||||
)
|
||||
|
||||
if not exist build\ (mkdir build)
|
||||
cd build
|
||||
|
||||
if not exist CMakeCache.txt (cmake -GNinja -DBOARD=%BOARD% .. || exit /b)
|
||||
|
||||
:: We need generated headers to be consumed by `cinterop`,
|
||||
:: so we preconfigure the project with `make zephyr`.
|
||||
ninja zephyr
|
||||
|
||||
call %DIR%\c_interop\platforms\%BOARD%.bat
|
||||
|
||||
del program.o
|
||||
|
||||
mkdir %DIR%\build\kotlin
|
||||
|
||||
call konanc %DIR%/src/main.kt ^
|
||||
-target zephyr_%BOARD% ^
|
||||
-r %DIR%/c_interop/platforms/build ^
|
||||
-l %BOARD% ^
|
||||
-opt -g -o %DIR%/build/kotlin/program || exit /b
|
||||
|
||||
ninja || exit /b
|
||||
|
||||
:: ninja flash
|
||||
::
|
||||
:: For our STM32 boards the OpenOCD unable to flash the binary,
|
||||
:: so we go with the following alternative utility:
|
||||
|
||||
echo.
|
||||
echo Now run 'ninja flash' to flash the .bin to the card.
|
||||
echo.
|
||||
echo Or, if that doesn't work, like, for example if you have an stm32f4-disco,
|
||||
echo run the following command:
|
||||
echo st-flash --reset write build/zephyr/zephyr.bin 0x08000000
|
||||
echo.
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BOARD=stm32f4_disco
|
||||
export ZEPHYR_BASE="PLEASE_SET_ZEPHYR_BASE"
|
||||
# By default `modules` directory is installed alongside ZEPHYR_BASE
|
||||
export ZEPHYR_MODULES_DIR="PLEASE_SET_ZEPHYR_MODULES_TOO"
|
||||
|
||||
if [ "$ZEPHYR_BASE" == "PLEASE_SET_ZEPHYR_BASE" ] ; then
|
||||
echo "Please set ZEPHYR_BASE in this build.sh."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export KONAN_DATA_DIR=$HOME/.konan
|
||||
export KONAN_DEPS=$KONAN_DATA_DIR/dependencies
|
||||
|
||||
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
|
||||
if [ -z "$KONAN_HOME" ]; then
|
||||
PATH="$DIR/../../dist/bin:$DIR/../../bin:$PATH"
|
||||
else
|
||||
PATH="$KONAN_HOME/bin:$PATH"
|
||||
fi
|
||||
|
||||
if [ x$TARGET == x ]; then
|
||||
case "$OSTYPE" in
|
||||
darwin*) TOOLCHAIN=gcc-arm-none-eabi-7-2017-q4-major-mac ;;
|
||||
linux*) TOOLCHAIN=gcc-arm-none-eabi-7-2017-q4-major-linux ;;
|
||||
*) echo "unknown: $OSTYPE" && exit 1;;
|
||||
esac
|
||||
fi
|
||||
|
||||
GNU_ARM="$KONAN_DEPS/$TOOLCHAIN"
|
||||
|
||||
rm -rf $DIR/build || exit 1
|
||||
mkdir -p $DIR/build && cd $DIR/build
|
||||
|
||||
export ZEPHYR_TOOLCHAIN_VARIANT=gnuarmemb
|
||||
export GNUARMEMB_TOOLCHAIN_PATH=$GNU_ARM
|
||||
|
||||
[ -f CMakeCache.txt ] || cmake -DCMAKE_VERBOSE_MAKEFILE=ON -DBOARD=$BOARD .. || exit 1
|
||||
|
||||
# We need generated headers to be consumed by `cinterop`,
|
||||
# so we preconfigure the project with `make zephyr`.
|
||||
make zephyr
|
||||
|
||||
. $DIR/c_interop/platforms/$BOARD.sh
|
||||
|
||||
rm -f program.o
|
||||
|
||||
mkdir -p $DIR/build/kotlin
|
||||
|
||||
konanc $DIR/src/main.kt \
|
||||
-target zephyr_$BOARD \
|
||||
-r $DIR/c_interop/platforms/build \
|
||||
-l $BOARD \
|
||||
-opt -g -o $DIR/build/kotlin/program || exit 1
|
||||
|
||||
make || exit 1
|
||||
|
||||
# make flash
|
||||
#
|
||||
# For our STM32 boards the OpenOCD unable to flash the binary,
|
||||
# so we go with the following alternative utility:
|
||||
|
||||
echo
|
||||
echo "Now run 'make flash' to flash the .bin to the card."
|
||||
echo
|
||||
echo "Or, if that doesn't work, like, for example if you have an stm32f4-disco,"
|
||||
echo "run the following command:"
|
||||
echo "st-flash --reset write build/zephyr/zephyr.bin 0x08000000"
|
||||
echo
|
||||
@@ -1,36 +0,0 @@
|
||||
::
|
||||
:: Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
:: that can be found in the license/LICENSE.txt file.
|
||||
::
|
||||
|
||||
:: This is board specific.
|
||||
|
||||
if "%BOARD%" == "" (
|
||||
echo This script is to be included by build.bat
|
||||
exit /b
|
||||
)
|
||||
|
||||
:: TODO: we need a robust automated way to ask Zephyr for
|
||||
:: all these %ZEPHYR_BASE% based paths and the proper defines.
|
||||
:: TODO: The D flag in `-compilerOpts -Dxxx` sequence is interpreted to be a jvm property,
|
||||
:: so we workaround the using -Xclang.
|
||||
|
||||
call cinterop -def %DIR%/c_interop/platforms/%BOARD%.def ^
|
||||
-pkg platform.zephyr.%BOARD% ^
|
||||
-compilerOpts "-Xclang -DSTM32F407xx" ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/kernel/include ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/arch/arm/include ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/soc/arm/st_stm32/stm32f4 ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/soc/arm/st_stm32/common ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/boards/arm/%BOARD% ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/include ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/include/drivers ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/ext/hal/cmsis/Include ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/ext/hal/st/stm32cube/stm32f4xx/soc ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/ext/hal/st/stm32cube/stm32f4xx/drivers/include ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/ext/hal/st/stm32cube/stm32f4xx/drivers/include/Legacy ^
|
||||
-compilerOpts -I%ZEPHYR_BASE%/drivers ^
|
||||
-compilerOpts -I%DIR%/build/zephyr/include/generated ^
|
||||
-compilerOpts -I%DIR%/build/zephyr/include/generated/syscalls ^
|
||||
-o %DIR%/c_interop/platforms/build/%BOARD% ^
|
||||
-target zephyr_%BOARD% || exit /b
|
||||
@@ -1,10 +0,0 @@
|
||||
#
|
||||
# Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
# that can be found in the license/LICENSE.txt file.
|
||||
#
|
||||
|
||||
# Keep this one board independent.
|
||||
|
||||
headers = autoconf.h zephyr.h generated_dts_board.h device.h gpio.h
|
||||
compilerOpts = -DKERNEL -DUSE_FULL_LL_DRIVER -DUSE_HAL_DRIVER -D_FORTIFY_SOURCE=2 -D__ZEPHYR__=1 -ffreestanding -std=c99 -nostdinc
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#
|
||||
# Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
# that can be found in the license/LICENSE.txt file.
|
||||
#
|
||||
|
||||
# This is board specific.
|
||||
|
||||
if [ x"$BOARD" == x ]; then
|
||||
echo "This script is to be included by build.sh"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# TODO: we need a robust automated way to ask Zephyr for
|
||||
# all these $ZEPHYR_BASE based paths and the proper defines.
|
||||
# TODO: The D flag in `-compilerOpts -Dxxx` sequence is interpreted to be a jvm property,
|
||||
# so we workaround the using -Xclang.
|
||||
|
||||
cinterop -def $DIR/c_interop/platforms/$BOARD.def \
|
||||
-pkg platform.zephyr.$BOARD \
|
||||
-compilerOpts '-Xclang -DSTM32F407xx' \
|
||||
-compilerOpts -I$ZEPHYR_BASE/kernel/include \
|
||||
-compilerOpts -I$ZEPHYR_BASE/arch/arm/include \
|
||||
-compilerOpts -I$ZEPHYR_BASE/soc/arm/st_stm32/stm32f4 \
|
||||
-compilerOpts -I$ZEPHYR_BASE/soc/arm/st_stm32/common \
|
||||
-compilerOpts -I$ZEPHYR_BASE/boards/arm/$BOARD \
|
||||
-compilerOpts -I$ZEPHYR_BASE/include \
|
||||
-compilerOpts -I$ZEPHYR_BASE/include/drivers \
|
||||
-compilerOpts -I$ZEPHYR_BASE/ext/hal/cmsis/Include \
|
||||
-compilerOpts -IZEPHYR_MODULES_DIR/hal/stm32/stm32cube/stm32f4xx/soc \
|
||||
-compilerOpts -IZEPHYR_MODULES_DIR/hal/stm32/stm32cube/stm32f4xx/drivers/include \
|
||||
-compilerOpts -IZEPHYR_MODULES_DIR/hal/stm32/stm32cube/stm32f4xx/drivers/include/Legacy \
|
||||
-compilerOpts -I$ZEPHYR_BASE/drivers \
|
||||
-compilerOpts -I$DIR/build/zephyr/include/generated \
|
||||
-compilerOpts -I$DIR/build/zephyr/include/generated/syscalls \
|
||||
-o $DIR/c_interop/platforms/build/$BOARD \
|
||||
-target zephyr_$BOARD || exit 1
|
||||
@@ -1,5 +0,0 @@
|
||||
CONFIG_CPLUSPLUS=n
|
||||
CONFIG_PRINTK=y
|
||||
CONFIG_BOOT_BANNER=y
|
||||
CONFIG_BUILD_OUTPUT_BIN=y
|
||||
CONFIG_NEWLIB_LIBC=y
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
import platform.zephyr.stm32f4_disco.*
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
fun blinky(value: Int) {
|
||||
|
||||
val port = LED0_GPIO_CONTROLLER
|
||||
val led = LED0_GPIO_PIN
|
||||
var toggler = false
|
||||
val dev = device_get_binding(port)
|
||||
|
||||
gpio_pin_configure(dev, led.convert(), GPIO_DIR_OUT)
|
||||
|
||||
while (true) {
|
||||
/* Set pin to HIGH/LOW every 1 second */
|
||||
gpio_pin_write(dev, led.convert(), if (toggler) 1U else 0U);
|
||||
toggler = !toggler
|
||||
k_sleep(1000 * value);
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
blinky(1)
|
||||
}
|
||||
Reference in New Issue
Block a user