Native: remove samples that are not used in tests

This commit is contained in:
Svyatoslav Scherbina
2022-08-26 11:26:51 +02:00
committed by Space
parent d22cccdc4d
commit 71b8089f33
72 changed files with 0 additions and 3992 deletions
-3
View File
@@ -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
-16
View File
@@ -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
-40
View File
@@ -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()}")
}
-32
View File
@@ -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

-48
View File
@@ -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")
}
}
}
}
@@ -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>>
)
@@ -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()
@@ -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 }
}
}
@@ -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() ?: ""
@@ -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
)
-55
View File
@@ -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.
-71
View File
@@ -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
-5
View File
@@ -1,5 +0,0 @@
CONFIG_CPLUSPLUS=n
CONFIG_PRINTK=y
CONFIG_BOOT_BANNER=y
CONFIG_BUILD_OUTPUT_BIN=y
CONFIG_NEWLIB_LIBC=y
-28
View File
@@ -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)
}