Compare commits
No commits in common. "master" and "v0.1.6-release" have entirely different histories.
master
...
v0.1.6-rel
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,4 +5,3 @@
|
|||||||
/gradle/wrapper/gradle-wrapper.properties
|
/gradle/wrapper/gradle-wrapper.properties
|
||||||
/node_modules
|
/node_modules
|
||||||
.kotlin
|
.kotlin
|
||||||
/.gigaide/gigaide.properties
|
|
||||||
|
12
README.md
12
README.md
@ -6,12 +6,6 @@ in native targets.
|
|||||||
|
|
||||||
# Recent changes
|
# Recent changes
|
||||||
|
|
||||||
- 0.1.12 published on all platforms. many small additions.
|
|
||||||
|
|
||||||
- 0.1.11 added interesting collection classes: auto-sorted list with comparator, expirable cache, etc.
|
|
||||||
|
|
||||||
- 0.1.7 built with kotlin 2.0.20 which contains important fix in wasmJS
|
|
||||||
|
|
||||||
- 0.1.6 add many useful features, added support to wasmJS and all other platforms. Note to wasmJS: it appears to be a bug in wasm compiler so BipackDecoder could cause wasm loading problem.
|
- 0.1.6 add many useful features, added support to wasmJS and all other platforms. Note to wasmJS: it appears to be a bug in wasm compiler so BipackDecoder could cause wasm loading problem.
|
||||||
|
|
||||||
- 0.1.1: added serialized KVStorage with handy implementation on JVM and JS platforms and some required synchronization
|
- 0.1.1: added serialized KVStorage with handy implementation on JVM and JS platforms and some required synchronization
|
||||||
@ -21,10 +15,6 @@ in native targets.
|
|||||||
|
|
||||||
The last 1.8-based version is 0.0.8. Some fixes are not yet backported to it pls leave an issue of needed.
|
The last 1.8-based version is 0.0.8. Some fixes are not yet backported to it pls leave an issue of needed.
|
||||||
|
|
||||||
# Documentation
|
|
||||||
|
|
||||||
Aside of the samples in this readme please see [library documentation](https://code.sergeych.net/docs/mp_bintools/).
|
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
Add our maven:
|
Add our maven:
|
||||||
@ -41,7 +31,7 @@ And add dependency to the proper place in your project like this:
|
|||||||
```kotlin
|
```kotlin
|
||||||
dependencies {
|
dependencies {
|
||||||
// ...
|
// ...
|
||||||
implementation("net.sergeych:mp_bintools:0.1.12")
|
implementation("net.sergeych:mp_bintools:0.1.0")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform") version "2.0.21"
|
kotlin("multiplatform") version "2.0.0"
|
||||||
kotlin("plugin.serialization") version "2.0.21"
|
kotlin("plugin.serialization") version "2.0.0"
|
||||||
id("org.jetbrains.dokka") version "1.9.20"
|
id("org.jetbrains.dokka") version "1.9.20"
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val serialization_version = "1.6.5-SNAPSHOT"
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "0.1.12"
|
version = "0.1.6"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -18,7 +18,15 @@ repositories {
|
|||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(8)
|
jvmToolchain(8)
|
||||||
jvm()
|
jvm {
|
||||||
|
// compilations.all {
|
||||||
|
// kotlinOptions.jvmTarget = "1.8"
|
||||||
|
// }
|
||||||
|
withJava()
|
||||||
|
testRuns["test"].executionTask.configure {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
}
|
||||||
js {
|
js {
|
||||||
browser()
|
browser()
|
||||||
nodejs()
|
nodejs()
|
||||||
@ -33,7 +41,6 @@ kotlin {
|
|||||||
linuxArm64()
|
linuxArm64()
|
||||||
mingwX64()
|
mingwX64()
|
||||||
|
|
||||||
@OptIn(ExperimentalWasmDsl::class)
|
|
||||||
wasmJs {
|
wasmJs {
|
||||||
browser()
|
browser()
|
||||||
binaries.executable()
|
binaries.executable()
|
||||||
@ -54,10 +61,10 @@ kotlin {
|
|||||||
}
|
}
|
||||||
val commonMain by getting {
|
val commonMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
|
||||||
// this is actually a bug: we need only the core, but bare core causes strange errors
|
// this is actually a bug: we need only the core, but bare core causes strange errors
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
|
||||||
api("net.sergeych:mp_stools:[1.5.2,)")
|
api("net.sergeych:mp_stools:[1.4.7,)")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,8 +83,6 @@ kotlin {
|
|||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(kotlin("test"))
|
implementation(kotlin("test"))
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jvmMain by getting
|
val jvmMain by getting
|
||||||
@ -92,11 +97,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val wasmJsTest by getting {
|
val wasmJsTest by getting
|
||||||
dependencies {
|
|
||||||
// implementation("org.jetbrains.kotlinx:kotlinx-browser:0.3")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
kotlin.js.compiler=ir
|
kotlin.js.compiler=ir
|
||||||
kotlin.mpp.applyDefaultHierarchyTemplate=false
|
|
||||||
|
41
gradlew
vendored
41
gradlew
vendored
@ -55,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@ -80,13 +80,11 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@ -133,22 +131,29 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
@ -205,6 +214,12 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
15
gradlew.bat
vendored
15
gradlew.bat
vendored
@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@ -25,7 +25,8 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,98 +0,0 @@
|
|||||||
package net.sergeych.bintools
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import net.sergeych.mp_tools.encodeToBase64Compact
|
|
||||||
import kotlin.math.min
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bytes sequence with comparison, concatenation, and string representation,
|
|
||||||
* could be used as hash keys for pure binary values, etc.
|
|
||||||
*/
|
|
||||||
@Suppress("unused")
|
|
||||||
@Serializable
|
|
||||||
class ByteChunk(val data: UByteArray): Comparable<ByteChunk> {
|
|
||||||
|
|
||||||
val size: Int get() = data.size
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Per-byte comparison also of different length. From two chunks
|
|
||||||
* of different size but equal beginning, the shorter is considered
|
|
||||||
* the smaller.
|
|
||||||
*/
|
|
||||||
override fun compareTo(other: ByteChunk): Int {
|
|
||||||
val limit = min(size, other.size)
|
|
||||||
for( i in 0 ..< limit) {
|
|
||||||
val own = data[i]
|
|
||||||
val their = other.data[i]
|
|
||||||
if( own < their) return -1
|
|
||||||
else if( own > their) return 1
|
|
||||||
}
|
|
||||||
if( size < other.size ) return -1
|
|
||||||
if( size > other.size ) return 1
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Equal chunks means content equality.
|
|
||||||
*/
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (other !is ByteChunk) return false
|
|
||||||
|
|
||||||
return data contentEquals other.data
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Content-based hash code
|
|
||||||
*/
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return data.contentHashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* hex representation of data
|
|
||||||
*/
|
|
||||||
override fun toString(): String = hex
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hex encoded data
|
|
||||||
*/
|
|
||||||
val hex by lazy { data.encodeToHex() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* human-readable dump
|
|
||||||
*/
|
|
||||||
val dump by lazy { data.toDump() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lazy encode to base64 with url alphabet, without trailing fill '=' characters.
|
|
||||||
*/
|
|
||||||
val base64 by lazy { data.asByteArray().encodeToBase64Compact() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lazy (cached) view of [data] as ByteArray
|
|
||||||
*/
|
|
||||||
val asByteArray: ByteArray by lazy { data.asByteArray() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Concatenate two chunks and return new one
|
|
||||||
*/
|
|
||||||
operator fun plus(other: ByteChunk): ByteChunk = ByteChunk(data + other.data)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromHex(hex: String): ByteChunk = ByteChunk(hex.decodeHex().asUByteArray())
|
|
||||||
fun random(size: Int=16): ByteChunk = Random.nextBytes(size).asChunk()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the representation of this array as ByteChunk; it does not copy the data.
|
|
||||||
*/
|
|
||||||
fun ByteArray.asChunk() = ByteChunk(this.asUByteArray())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the representation of this array as ByteChunk; it does not copy the data.
|
|
||||||
*/
|
|
||||||
@Suppress("unused")
|
|
||||||
fun UByteArray.asChunk() = ByteChunk(this)
|
|
@ -2,18 +2,11 @@ package net.sergeych.bintools
|
|||||||
|
|
||||||
import net.sergeych.bipack.BipackDecoder
|
import net.sergeych.bipack.BipackDecoder
|
||||||
import net.sergeych.bipack.BipackEncoder
|
import net.sergeych.bipack.BipackEncoder
|
||||||
import net.sergeych.mp_logger.LogTag
|
|
||||||
import net.sergeych.mp_logger.Loggable
|
|
||||||
import net.sergeych.mp_logger.debug
|
|
||||||
import net.sergeych.synctools.AtomicCounter
|
|
||||||
import net.sergeych.synctools.ProtectedOp
|
import net.sergeych.synctools.ProtectedOp
|
||||||
import net.sergeych.synctools.WaitHandle
|
import net.sergeych.synctools.WaitHandle
|
||||||
import net.sergeych.synctools.withLock
|
import net.sergeych.synctools.withLock
|
||||||
|
|
||||||
private val ac = AtomicCounter(0)
|
class DataKVStorage(private val provider: DataProvider) : KVStorage {
|
||||||
|
|
||||||
class DataKVStorage(private val provider: DataProvider) : KVStorage,
|
|
||||||
Loggable by LogTag("DKVS${ac.incrementAndGet()}") {
|
|
||||||
|
|
||||||
data class Lock(val name: String) {
|
data class Lock(val name: String) {
|
||||||
private val exclusive = ProtectedOp()
|
private val exclusive = ProtectedOp()
|
||||||
@ -27,6 +20,8 @@ class DataKVStorage(private val provider: DataProvider) : KVStorage,
|
|||||||
exclusive.withLock {
|
exclusive.withLock {
|
||||||
if (readerCount == 0) {
|
if (readerCount == 0) {
|
||||||
return f()
|
return f()
|
||||||
|
} else {
|
||||||
|
println("can't lock $this: count is $readerCount")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pulses.await()
|
pulses.await()
|
||||||
@ -54,16 +49,19 @@ class DataKVStorage(private val provider: DataProvider) : KVStorage,
|
|||||||
access.withLock {
|
access.withLock {
|
||||||
// TODO: read keys
|
// TODO: read keys
|
||||||
for (fn in provider.list()) {
|
for (fn in provider.list()) {
|
||||||
debug { "Scanning: $fn" }
|
println("Scanning: $fn")
|
||||||
if (fn.endsWith(".d")) {
|
if (fn.endsWith(".d")) {
|
||||||
val id = fn.dropLast(2).toInt(16)
|
val id = fn.dropLast(2).toInt(16)
|
||||||
debug { "found data record: $fn -> $id" }
|
println("found data record: $fn -> $id")
|
||||||
|
|
||||||
val name = provider.read(fn) { BipackDecoder.decode<String>(it) }
|
val name = provider.read(fn) { BipackDecoder.decode<String>(it) }
|
||||||
|
println("Key=$name")
|
||||||
keyIds[name] = id
|
keyIds[name] = id
|
||||||
if (id > lastId) lastId = id
|
if (id > lastId) lastId = id
|
||||||
} else debug { "ignoring record $fn" }
|
} else println("ignoring record $fn")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
println("initialized, ${keyIds.size} records found, lastId=$lastId")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -99,7 +97,7 @@ class DataKVStorage(private val provider: DataProvider) : KVStorage,
|
|||||||
lock.lockExclusive { provider.write(recordName(id), f) }
|
lock.lockExclusive { provider.write(recordName(id), f) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteEntry(name: String) {
|
fun deleteEntry(name: String) {
|
||||||
// fast pre-check:
|
// fast pre-check:
|
||||||
if (name !in keyIds) return
|
if (name !in keyIds) return
|
||||||
// global lock: we can't now detect concurrent delete + write ops, so exclusive:
|
// global lock: we can't now detect concurrent delete + write ops, so exclusive:
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package net.sergeych.bintools
|
package net.sergeych.bintools
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.serializer
|
||||||
import net.sergeych.bipack.BipackDecoder
|
import net.sergeych.bipack.BipackDecoder
|
||||||
import net.sergeych.bipack.BipackEncoder
|
import net.sergeych.bipack.BipackEncoder
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,7 +43,7 @@ interface KVStorage {
|
|||||||
* Default implementation uses [keys]. You may override it for performance
|
* Default implementation uses [keys]. You may override it for performance
|
||||||
*/
|
*/
|
||||||
fun clear() {
|
fun clear() {
|
||||||
for (k in keys.toList()) this[k] = null
|
for (k in keys) this[k] = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,50 +93,48 @@ inline fun <reified T:Any>KVStorage.read(key: String): T? =
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
inline fun <reified T:Any>KVStorage.load(key: String): T? = read(key)
|
inline fun <reified T:Any>KVStorage.load(key: String): T? = read(key)
|
||||||
|
|
||||||
inline operator fun <reified T: Any> KVStorage.invoke(defaultValue: T,overrideName: String? = null) =
|
inline operator fun <reified T> KVStorage.invoke(defaultValue: T,overrideName: String? = null) =
|
||||||
KVStorageDelegate(this, serializer<T>(), defaultValue, overrideName)
|
KVStorageDelegate<T>(this, typeOf<T>(), defaultValue, overrideName)
|
||||||
|
|
||||||
inline fun <reified T: Any> KVStorage.stored(defaultValue: T, overrideName: String? = null) =
|
inline fun <reified T> KVStorage.stored(defaultValue: T, overrideName: String? = null) =
|
||||||
KVStorageDelegate(this, serializer<T>(), defaultValue, overrideName)
|
KVStorageDelegate<T>(this, typeOf<T>(), defaultValue, overrideName)
|
||||||
|
|
||||||
inline fun <reified T: Any> KVStorage.optStored(overrideName: String? = null) =
|
inline fun <reified T> KVStorage.optStored(overrideName: String? = null) =
|
||||||
KVStorageOptDelegate<T>(this, serializer<T>(),overrideName)
|
KVStorageDelegate<T?>(this, typeOf<T?>(), null, overrideName)
|
||||||
|
|
||||||
class KVStorageDelegate<T: Any>(
|
class KVStorageDelegate<T>(
|
||||||
private val storage: KVStorage,
|
private val storage: KVStorage,
|
||||||
private val serializer: KSerializer<T>,
|
type: KType,
|
||||||
private val defaultValue: T,
|
private val defaultValue: T,
|
||||||
private val overrideName: String? = null,
|
private val overrideName: String? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private fun name(property: KProperty<*>): String = overrideName ?: property.name
|
private fun name(property: KProperty<*>): String = overrideName ?: property.name
|
||||||
|
|
||||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
|
private var cachedValue: T = defaultValue
|
||||||
storage.get(name(property))?.let { BipackDecoder.decode(serializer, it) }
|
private var cacheReady = false
|
||||||
?: defaultValue
|
private val serializer = serializer(type)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
|
if (cacheReady) return cachedValue
|
||||||
|
val data = storage.get(name(property))
|
||||||
|
println("Got data: ${data?.toDump()}")
|
||||||
|
if (data == null)
|
||||||
|
cachedValue = defaultValue
|
||||||
|
else
|
||||||
|
cachedValue = BipackDecoder.decode(data.toDataSource(), serializer) as T
|
||||||
|
cacheReady = true
|
||||||
|
return cachedValue
|
||||||
|
}
|
||||||
|
|
||||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||||
storage[name(property)] = BipackEncoder.encode(serializer, value)
|
// if (!cacheReady || value != cachedValue) {
|
||||||
}
|
cachedValue = value
|
||||||
}
|
cacheReady = true
|
||||||
|
println("set ${name(property)} to ${BipackEncoder.encode(serializer, value).toDump()}")
|
||||||
class KVStorageOptDelegate<T: Any>(
|
|
||||||
private val storage: KVStorage,
|
|
||||||
private val serializer: KSerializer<T>,
|
|
||||||
private val overrideName: String? = null,
|
|
||||||
) {
|
|
||||||
private fun name(property: KProperty<*>): String = overrideName ?: property.name
|
|
||||||
|
|
||||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? =
|
|
||||||
storage.get(name(property))?.let{
|
|
||||||
BipackDecoder.decode(serializer, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
|
|
||||||
if (value == null)
|
|
||||||
storage.delete(name(property))
|
|
||||||
else
|
|
||||||
storage[name(property)] = BipackEncoder.encode(serializer, value)
|
storage[name(property)] = BipackEncoder.encode(serializer, value)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ package net.sergeych.bintools
|
|||||||
*
|
*
|
||||||
* Note that the cost, [MRUCache] is slower than [MutableMap].
|
* Note that the cost, [MRUCache] is slower than [MutableMap].
|
||||||
*/
|
*/
|
||||||
@Deprecated("moved to net.sergeych.collections", ReplaceWith("net.sergeych.collections.MRUCache"))
|
|
||||||
class MRUCache<K,V>(val maxSize: Int,
|
class MRUCache<K,V>(val maxSize: Int,
|
||||||
private val cache: LinkedHashMap<K,V> = LinkedHashMap()
|
private val cache: LinkedHashMap<K,V> = LinkedHashMap()
|
||||||
): MutableMap<K,V> by cache {
|
): MutableMap<K,V> by cache {
|
||||||
|
@ -84,11 +84,13 @@ private val hexDigits = "0123456789ABCDEF"
|
|||||||
|
|
||||||
fun Long.encodeToHex(length: Int = 0): String {
|
fun Long.encodeToHex(length: Int = 0): String {
|
||||||
var result = ""
|
var result = ""
|
||||||
var value = this.toULong()
|
var value = this
|
||||||
|
val end = if( value >= 0 ) 0L else -1L
|
||||||
|
// if (value < 0) throw IllegalArgumentException("cant convert to hex negative (ambiguous)")
|
||||||
do {
|
do {
|
||||||
result = hexDigits[(value and 0x0fu).toInt()] + result
|
result = hexDigits[(value and 0x0f).toInt()] + result
|
||||||
value = value shr 4
|
value = value shr 4
|
||||||
} while (value != 0UL)
|
} while (value != end)
|
||||||
while (result.length < length) result = "0" + result
|
while (result.length < length) result = "0" + result
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -111,8 +113,6 @@ fun Collection<Byte>.encodeToHex(separator: String = " "): String = joinToString
|
|||||||
|
|
||||||
fun ByteArray.toDump(wide: Boolean = false): String = toDumpLines(wide).joinToString("\n")
|
fun ByteArray.toDump(wide: Boolean = false): String = toDumpLines(wide).joinToString("\n")
|
||||||
|
|
||||||
fun UByteArray.toDump(wide: Boolean = false): String = asByteArray().toDumpLines(wide).joinToString("\n")
|
|
||||||
|
|
||||||
fun ByteArray.toDumpLines(wide: Boolean = false): List<String> {
|
fun ByteArray.toDumpLines(wide: Boolean = false): List<String> {
|
||||||
|
|
||||||
val lineSize = if (wide) 32 else 16
|
val lineSize = if (wide) 32 else 16
|
||||||
|
@ -3,19 +3,22 @@ package net.sergeych.bipack
|
|||||||
import kotlinx.serialization.SerialInfo
|
import kotlinx.serialization.SerialInfo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To be used with [kotlinx.serialization.Serializable]. Allows serialized classes to be
|
* If this annotation is presented in some @Serializable class definition, its instances
|
||||||
* extended by _adding members with default initializers_ __to the end of the constructor list__.
|
* will be serialized with the leading number of fields. This allows extending class later
|
||||||
|
* providing new parameters __to the end of the class__ and _with default values__.
|
||||||
*
|
*
|
||||||
* This annotation makes Bipack to insert fields count before the
|
* __IMPORTANT NOTE__. Since version 0.0.7 it's been also possible to use default values
|
||||||
* serialized data. It then checks it on deserializing to fill not serialized fields will
|
* for non-serialized fields after the end-of-data. If the source reports it correctly, e.g.
|
||||||
* default values.
|
* [net.sergeych.bintools.DataSource.isEnd] returns true, the unset fields are initialized
|
||||||
|
* with default value. This approach ___is not working when the loading instance is not the last
|
||||||
|
* in the deciding array!___, still it is useful to decode isolated objects. We recommend to
|
||||||
|
* use [Extendable] where needed and possible.
|
||||||
*
|
*
|
||||||
* Note that since 0.0.7 the same behavior could be achieved by serializing each instance in the
|
* Whe deserializing such instances from previous version binaries, the new parameters
|
||||||
* array as Bipack correctly processes end-of-data by filling missing fields with default values,
|
* will get default values.
|
||||||
* using `Extendable` is more convenient and save some space, most of the time.
|
|
||||||
*
|
*
|
||||||
* _Please note that without this annotation it could be impossible to deserialize old versions of
|
* Serialized data of classes not market as ExtendableFormat could not be changed without
|
||||||
* the class, in particular, in array, inner fields, etc._
|
* breaking compatibility with existing serialized data.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@SerialInfo
|
@SerialInfo
|
||||||
|
@ -1,342 +0,0 @@
|
|||||||
package net.sergeych.collections
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import net.sergeych.bintools.encodeToHex
|
|
||||||
import net.sergeych.collections.BitSet.Companion.MAX_VALUE
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bitset is a serializable set of __positive__ integers represented as bits in long array.
|
|
||||||
* This ought to be more effective, and sure it is more effective in serialized form,
|
|
||||||
* as long as maximum stored number is not too big, We recommend limit of 10-20k.
|
|
||||||
*
|
|
||||||
* It limits hold values to [MAX_VALUE] anyway to avoid fast memory depletion
|
|
||||||
*
|
|
||||||
* It has optimized bitwise operation based versions of [retainAll] and [removeAll],
|
|
||||||
* [intersect] and [isEmpty], that are used if their arguments, where used, are `BitSet`
|
|
||||||
* instances. Also [equals] works faster with BitSet and BitSet.
|
|
||||||
*
|
|
||||||
* Use [bitSetOf] and [bitSetOfEnum] to simply create bitsets.
|
|
||||||
*
|
|
||||||
* It also contains syntax sugar to work with enums directly:
|
|
||||||
*
|
|
||||||
* - [includes] and [includesAll] to check that enum is in set
|
|
||||||
* - [insert], [insertAll], [delete], and [deleteAll] to manipulate enum values
|
|
||||||
* - [toEnumSet] to convert to set of enums
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
class BitSet(private val bits: MutableList<Long> = mutableListOf()) : MutableSet<Int> {
|
|
||||||
|
|
||||||
fun set(element: Int, value: Boolean = true) = setBit(element, value)
|
|
||||||
|
|
||||||
fun clear(element: Int) = set(element, false)
|
|
||||||
|
|
||||||
operator fun plusAssign(element: Int) {
|
|
||||||
set(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun plus(element: Int): BitSet = BitSet(bits.toMutableList()).apply { set(element) }
|
|
||||||
|
|
||||||
operator fun minusAssign(element: Int) {
|
|
||||||
clear(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun minus(element: Int): BitSet = BitSet(bits.toMutableList()).apply { clear(element) }
|
|
||||||
|
|
||||||
private fun setBit(element: Int, value: Boolean): Boolean {
|
|
||||||
require(element >= 0, { "only positive numbers are allowed" })
|
|
||||||
require(element < MAX_VALUE, { "maximum value allowed is $MAX_VALUE" })
|
|
||||||
val offset = element shr 6
|
|
||||||
val bit = element % 64
|
|
||||||
return if (value) {
|
|
||||||
while (offset >= bits.size) bits.add(0)
|
|
||||||
val last = bits[offset] and masks[bit]
|
|
||||||
bits[offset] = bits[offset] or masks[bit]
|
|
||||||
last != 0L
|
|
||||||
} else {
|
|
||||||
if (offset < bits.size) {
|
|
||||||
// bigger, not existing means 0
|
|
||||||
val last = bits[offset] and masks[bit]
|
|
||||||
bits[offset] = bits[offset] and maskNot[bit]
|
|
||||||
last != 0L
|
|
||||||
} else {
|
|
||||||
// already 0: index not in bits:
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getBit(value: Int): Boolean {
|
|
||||||
val offset = value shr 6
|
|
||||||
if (offset >= bits.size) return false
|
|
||||||
val bit = value % 64
|
|
||||||
return (bits[offset] and masks[bit]) != 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun add(element: Int): Boolean = setBit(element, true)
|
|
||||||
|
|
||||||
override val size: Int
|
|
||||||
get() {
|
|
||||||
var count = 0
|
|
||||||
for (w in bits) {
|
|
||||||
for (m in masks) {
|
|
||||||
if ((w and m) != 0L) count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addAll(elements: Collection<Int>): Boolean {
|
|
||||||
var added = false
|
|
||||||
for (i in elements) if (setBit(i, true)) added = true
|
|
||||||
return added
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun clear() {
|
|
||||||
bits.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEmpty(): Boolean {
|
|
||||||
if (bits.isEmpty()) return true
|
|
||||||
for (w in bits) if (w != 0L) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toList(): List<Int> {
|
|
||||||
var value = 0
|
|
||||||
val result = mutableListOf<Int>()
|
|
||||||
for (w in bits) {
|
|
||||||
for (m in masks) {
|
|
||||||
if ((w and m) != 0L) result += value
|
|
||||||
value++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toHex(): String = bits.toString() + " " + bits.joinToString(" ") { it.encodeToHex() }
|
|
||||||
|
|
||||||
override fun iterator(): MutableIterator<Int> = object : MutableIterator<Int> {
|
|
||||||
private val i = toList().iterator()
|
|
||||||
private var last: Int? = null
|
|
||||||
override operator fun hasNext() = i.hasNext()
|
|
||||||
override fun next(): Int = i.next().also { last = it }
|
|
||||||
override fun remove() {
|
|
||||||
last?.let { clear(it) } ?: IllegalStateException("hasNext() was not called")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fastRetainAll(elements: BitSet): Boolean {
|
|
||||||
var result = false
|
|
||||||
for (i in bits.indices) {
|
|
||||||
if (i < elements.bits.size) {
|
|
||||||
val x = bits[i]
|
|
||||||
val y = x and elements.bits[i]
|
|
||||||
if (x != y) {
|
|
||||||
result = true
|
|
||||||
bits[i] = y
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (bits[i] != 0L) {
|
|
||||||
bits[i] = 0
|
|
||||||
result = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun retainAll(elements: Collection<Int>): Boolean {
|
|
||||||
return if (elements is BitSet)
|
|
||||||
fastRetainAll(elements)
|
|
||||||
else {
|
|
||||||
var value = 0
|
|
||||||
var result = false
|
|
||||||
for ((i, _w) in bits.withIndex()) {
|
|
||||||
var w = _w
|
|
||||||
for (m in masks) {
|
|
||||||
if ((w and m) != 0L && value !in elements) {
|
|
||||||
w = w and m.inv()
|
|
||||||
bits[i] = w
|
|
||||||
result = true
|
|
||||||
}
|
|
||||||
value++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeAll(elements: Collection<Int>): Boolean {
|
|
||||||
return if (elements is BitSet)
|
|
||||||
fastRemoveAll(elements)
|
|
||||||
else {
|
|
||||||
var value = 0
|
|
||||||
var result = false
|
|
||||||
for ((i, _w) in bits.withIndex()) {
|
|
||||||
var w = _w
|
|
||||||
for (m in masks) {
|
|
||||||
if ((w and m) != 0L && value in elements) {
|
|
||||||
w = w and m.inv()
|
|
||||||
bits[i] = w
|
|
||||||
result = true
|
|
||||||
}
|
|
||||||
value++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fastRemoveAll(elements: BitSet): Boolean {
|
|
||||||
var result = false
|
|
||||||
for (i in bits.indices) {
|
|
||||||
if (i < elements.bits.size) {
|
|
||||||
val x = bits[i]
|
|
||||||
val y = x and elements.bits[i].inv()
|
|
||||||
if (x != y) {
|
|
||||||
bits[i] = y
|
|
||||||
result = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println("fast2")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun remove(element: Int): Boolean = setBit(element, false)
|
|
||||||
|
|
||||||
override fun containsAll(elements: Collection<Int>): Boolean {
|
|
||||||
for (e in elements) if (e !in this) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toIntSet() = toList().toSet()
|
|
||||||
|
|
||||||
override fun contains(element: Int): Boolean = getBit(element)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that this set contains and ordinal of a given enum element.
|
|
||||||
*/
|
|
||||||
infix fun <E> includes(element: E)
|
|
||||||
where E : Enum<*> = contains(element.ordinal)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that this set contains all elements using its ordinals.
|
|
||||||
*/
|
|
||||||
infix fun <E> includesAll(elements: Collection<E>)
|
|
||||||
where E : Enum<*> = elements.all { it.ordinal in this }
|
|
||||||
|
|
||||||
fun intersect(other: Iterable<Int>): BitSet {
|
|
||||||
val result = toBitSet()
|
|
||||||
result.retainAll(other)
|
|
||||||
println("I: $this /\\ $other = $result")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return toList().toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks that this set contains at least one element with ordinal
|
|
||||||
*/
|
|
||||||
infix inline fun <reified E> includesAny(elements: Collection<E>): Boolean
|
|
||||||
where E : Enum<E> {
|
|
||||||
val ords = elements.map { it.ordinal }.toBitSet()
|
|
||||||
return !ords.intersect(this).isEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an independent copy of this bitset
|
|
||||||
*/
|
|
||||||
fun toBitSet() = BitSet(bits.toMutableList())
|
|
||||||
|
|
||||||
inline fun <reified T> toEnumSet(): Set<T>
|
|
||||||
where T : Enum<T> {
|
|
||||||
val values = enumValues<T>()
|
|
||||||
val result = mutableSetOf<T>()
|
|
||||||
for (i in this) result += values[i]
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert an element of an enum by its ordinal, much like [add].
|
|
||||||
*
|
|
||||||
* @return `true` if the element has actually been added, `false` if
|
|
||||||
* BitSet was not modified.
|
|
||||||
*/
|
|
||||||
fun <E> insert(element: E): Boolean
|
|
||||||
where E : Enum<*> = add(element.ordinal)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an element of an enum using its ordinal, much like [remove].
|
|
||||||
*
|
|
||||||
* @return `true` if the element has actually been removed, `false` if
|
|
||||||
* BitSet was not modified.
|
|
||||||
*/
|
|
||||||
fun <E> delete(element: E): Boolean
|
|
||||||
where E : Enum<*> = remove(element.ordinal)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert all elements using its ordinals, much like [addAll].
|
|
||||||
*
|
|
||||||
* @return `true` if at lease one element has actually been added, `false`
|
|
||||||
* if BitSet was not modified.
|
|
||||||
*/
|
|
||||||
fun <E> insertAll(element: Collection<E>): Boolean
|
|
||||||
where E : Enum<*> = addAll(element.map { it.ordinal })
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all the elements using its ordinals, much like [removeAll].
|
|
||||||
*
|
|
||||||
* @return `true` if at least one element has actually been removed, `false` if
|
|
||||||
* BitSet was not modified.
|
|
||||||
*/
|
|
||||||
fun <E> deleteAll(elements: Collection<E>): Boolean
|
|
||||||
where E : Enum<*> = removeAll(elements.map { it.ordinal })
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reduces storage size trying to compact storage. It might free some memory, depending
|
|
||||||
* on the platform implementation of lists and contents. Does not change stored values.
|
|
||||||
*/
|
|
||||||
fun compact() {
|
|
||||||
while( bits.isNotEmpty() && bits.last() == 0L ) bits.removeLast()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = bits.hashCode()
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if( other is BitSet ) {
|
|
||||||
compact(); other.compact()
|
|
||||||
return other.bits == this.bits
|
|
||||||
}
|
|
||||||
return toIntSet().equals(
|
|
||||||
if( other is Set<*>) other
|
|
||||||
else (other as Collection<*>).toSet()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val masks = Array(64) { (1L shl it) }
|
|
||||||
val maskNot = masks.map { it.inv() }.toLongArray()
|
|
||||||
|
|
||||||
// limit size to ≈ 100kb
|
|
||||||
const val MAX_VALUE = 8_388_106
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bitSetOf(vararg values: Int) = BitSet().apply {
|
|
||||||
for (i in values) add(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <E : Enum<*>> bitSetOfEnum(vararg values: E) =
|
|
||||||
BitSet().also {
|
|
||||||
for (v in values) it.add(v.ordinal)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun Iterable<Int>.toBitSet(): BitSet = BitSet().also { it.addAll(this) }
|
|
||||||
fun IntArray.toBitSet(): BitSet = BitSet().also { it.addAll(this.asIterable()) }
|
|
@ -1,168 +0,0 @@
|
|||||||
package net.sergeych.collections
|
|
||||||
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import kotlinx.datetime.Clock
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import net.sergeych.mptools.withReentrantLock
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MRU cache with expiration, with safe async concurrent access.
|
|
||||||
*
|
|
||||||
* Expired items are removed when accessing the map, when reading values or when putting
|
|
||||||
* it when [maxCapacity] is reached. See note about freeing resources below.
|
|
||||||
*
|
|
||||||
* It is much like [Map] and [MutableMap], but using suspend functions now limit usage of
|
|
||||||
* operator functions so we are not implementing it. Also, modification with [entries] is
|
|
||||||
* not allowed.
|
|
||||||
*
|
|
||||||
* Unlike [MRUCache], it drops expired values. Removing expired item is lazy, actual resource
|
|
||||||
* freeing could be delayed. To force actual removal use [cleanup].
|
|
||||||
*
|
|
||||||
* @param lifeTime how long the value should be kept
|
|
||||||
* @param maxCapacity if set, limits the capacity. Least Recent Used elements would be dropped
|
|
||||||
* to fit this parameter.
|
|
||||||
* @param onItemRemoved called when some item is removed for any reason (e.g. expiration or overwriting).
|
|
||||||
* Note that this call also suspends put variants until done
|
|
||||||
*/
|
|
||||||
class ExpirableAsyncCache<K, V>(
|
|
||||||
val lifeTime: Duration = 30.seconds,
|
|
||||||
val maxCapacity: Int? = null,
|
|
||||||
val onItemRemoved: (suspend (V) -> Unit)? = null
|
|
||||||
) {
|
|
||||||
|
|
||||||
class Slot<V>(
|
|
||||||
var value: V,
|
|
||||||
var lastUsedAt: Instant = Clock.System.now(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private val access = Mutex()
|
|
||||||
|
|
||||||
private val cache = mutableMapOf<K, Slot<V>>()
|
|
||||||
|
|
||||||
suspend fun get(key: K): V? {
|
|
||||||
return access.withReentrantLock {
|
|
||||||
cache.get(key)?.let {
|
|
||||||
val now = Clock.System.now()
|
|
||||||
println("lifetime $key: ${now - it.lastUsedAt}")
|
|
||||||
if (now - it.lastUsedAt > lifeTime) {
|
|
||||||
cache.remove(key)
|
|
||||||
onItemRemoved?.invoke(it.value)
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
it.lastUsedAt = now
|
|
||||||
it.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put the value for key. Calls [onItemRemoved] if needed.
|
|
||||||
* @return previous value or null
|
|
||||||
*/
|
|
||||||
suspend fun put(key: K, value: V): V? {
|
|
||||||
// insert may replace existing item, so we do it first:
|
|
||||||
return access.withLock {
|
|
||||||
cache[key]?.let {
|
|
||||||
if (value != it.value)
|
|
||||||
onItemRemoved?.invoke(it.value)
|
|
||||||
val oldValue = it.value
|
|
||||||
it.value = value
|
|
||||||
it.lastUsedAt = Clock.System.now()
|
|
||||||
oldValue
|
|
||||||
} ?: run {
|
|
||||||
// overflow could be caused by put, so put first
|
|
||||||
cache.put(key, Slot(value))
|
|
||||||
// now check size
|
|
||||||
fixSize()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun fixSize() {
|
|
||||||
maxCapacity?.let {
|
|
||||||
if (it >= cache.size) {
|
|
||||||
cache.remove(cache.minBy { it.value.lastUsedAt }.key)
|
|
||||||
?.also { onItemRemoved?.invoke(it.value) }
|
|
||||||
?.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all expired elements. This function is not needed unless you
|
|
||||||
* want to free resources associated with expired elements immediately.
|
|
||||||
*
|
|
||||||
* [onItemRemoved] is called for each removed item before returning.
|
|
||||||
*/
|
|
||||||
@Suppress("unused")
|
|
||||||
suspend fun cleanup() {
|
|
||||||
access.withLock {
|
|
||||||
val d = Clock.System.now()
|
|
||||||
for( e in cache.entries.toList()) {
|
|
||||||
if( d - e.value.lastUsedAt > lifeTime )
|
|
||||||
cache.remove(e.key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getOrDefault(key: K, value: V): V = get(key) ?: value
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Atomically get or put value to the cache.
|
|
||||||
*
|
|
||||||
* If there is expired existing value, [onItemRemoved] will be called for it
|
|
||||||
* before assigning new value.
|
|
||||||
*/
|
|
||||||
suspend fun getOrPut(key: K, defaultValue: suspend () -> V): V {
|
|
||||||
return access.withLock {
|
|
||||||
cache[key]?.let {
|
|
||||||
if( Clock.System.now() - it.lastUsedAt > lifeTime) {
|
|
||||||
onItemRemoved?.invoke(it.value)
|
|
||||||
}
|
|
||||||
it.lastUsedAt = Clock.System.now()
|
|
||||||
it.value
|
|
||||||
} ?: run {
|
|
||||||
val v = defaultValue()
|
|
||||||
cache[key] = Slot(v)
|
|
||||||
fixSize()
|
|
||||||
v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Entry<K, V>(override val key: K, override val value: V) : Map.Entry<K, V>
|
|
||||||
|
|
||||||
val entries: Set<Map.Entry<K, V>>
|
|
||||||
get() = cache.entries.map { Entry(it.key, it.value.value) }.toSet()
|
|
||||||
|
|
||||||
val keys: Set<K>
|
|
||||||
get() = cache.keys
|
|
||||||
|
|
||||||
val size: Int
|
|
||||||
get() = cache.size
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
val values: Collection<V>
|
|
||||||
get() = cache.values.map { it.value }
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
fun isEmpty(): Boolean = cache.isEmpty()
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
fun containsValue(value: V): Boolean {
|
|
||||||
for (v in cache.values)
|
|
||||||
if (v.value == value) return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
fun containsKey(key: K): Boolean = key in cache
|
|
||||||
|
|
||||||
operator fun contains(k: K): Boolean = k in cache
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
|||||||
package net.sergeych.collections
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Most Recently Used keys Cache.
|
|
||||||
* Maintains the specified size, removed least used elements on insertion. Element usage is
|
|
||||||
* when it is inserted, updated or accessed (with [get]). Least recently used (LRU) keys
|
|
||||||
* are automatically removed to maintain the [maxSize].
|
|
||||||
*
|
|
||||||
* Note that the cost, [MRUCache] is slower than [MutableMap].
|
|
||||||
*/
|
|
||||||
class MRUCache<K,V>(val maxSize: Int,
|
|
||||||
private val cache: LinkedHashMap<K,V> = LinkedHashMap()
|
|
||||||
): MutableMap<K,V> by cache {
|
|
||||||
|
|
||||||
private fun checkSize() {
|
|
||||||
while(cache.size > maxSize) {
|
|
||||||
cache.remove(cache.keys.first())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put the [value] associated with [key] which becomes MRU whether it existed in the cache or was added now.
|
|
||||||
*
|
|
||||||
* If [size] == [maxSize] LRU key will be dropped.
|
|
||||||
*
|
|
||||||
* @return old value for the [key] or null
|
|
||||||
*/
|
|
||||||
override fun put(key: K, value: V): V? {
|
|
||||||
// we need it to become MRU, so we remove it to clear its position
|
|
||||||
val oldValue = cache.remove(key)
|
|
||||||
// now we always add, not update, so it will become MRU element:
|
|
||||||
cache.put(key,value).also { checkSize() }
|
|
||||||
return oldValue
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put all the key-value pairs, this is exactly same as calling [put] in the same
|
|
||||||
* order. Note that is the [from] map is not linked and its size is greater than
|
|
||||||
* [maxSize], some unpredictable keys will not be added. To be exact, only last
|
|
||||||
* [maxSize] keys will be added by the order providing by [from] map entries
|
|
||||||
* enumerator.
|
|
||||||
*
|
|
||||||
* If from is [LinkedHashMap] or like, onl
|
|
||||||
*/
|
|
||||||
override fun putAll(from: Map<out K, V>) {
|
|
||||||
// maybe we should optimize it not to add unnecessary first keys
|
|
||||||
for( e in from) {
|
|
||||||
put(e.key,e.value)
|
|
||||||
checkSize()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value associated with the [key]. It makes the [key] a MRU (last to delete)
|
|
||||||
*/
|
|
||||||
override fun get(key: K): V? {
|
|
||||||
return cache[key]?.also {
|
|
||||||
cache.remove(key)
|
|
||||||
cache[key] = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = cache.toString()
|
|
||||||
}
|
|
@ -1,199 +0,0 @@
|
|||||||
package net.sergeych.collections
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically mutable sorted list based on binary search and a given comparing function.
|
|
||||||
* To construct list of comparable elements use [invoke]. There is a secondary constructor
|
|
||||||
* to use with existing [Comparator] instance.
|
|
||||||
*
|
|
||||||
* While the sorted list is mutable, it does not implement `MutableList` because indexed
|
|
||||||
* assignments are not possible keeping sort order; use [add] instead.
|
|
||||||
*
|
|
||||||
* It is possible to store several equal values and retrieve them all. See [add] and [addIfNotExists].
|
|
||||||
*/
|
|
||||||
class SortedList<T: Any>(
|
|
||||||
private val list: MutableList<T> = mutableListOf(),
|
|
||||||
private val compare: (T,T) -> Int
|
|
||||||
)
|
|
||||||
: List<T> by list
|
|
||||||
{
|
|
||||||
@Suppress("unused")
|
|
||||||
constructor(list: MutableList<T>, comparator: Comparator<T>)
|
|
||||||
: this(list,{ a, b -> comparator.compare(a,b) })
|
|
||||||
|
|
||||||
private fun binarySearch(element: T): Int {
|
|
||||||
var low = 0
|
|
||||||
var high = this.size - 1
|
|
||||||
|
|
||||||
while (low <= high) {
|
|
||||||
val mid = (low + high).ushr(1) // unsigned shift right, equivalent to integer division of sum by 2
|
|
||||||
val midVal = list[mid]
|
|
||||||
val cmp = compare(element,midVal)
|
|
||||||
|
|
||||||
when {
|
|
||||||
cmp < 0 -> high = mid - 1
|
|
||||||
cmp > 0 -> low = mid + 1
|
|
||||||
else -> return mid // key found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -(low + 1) // key not found, insertion point is -(low + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find any element equals to value using fast binary search.
|
|
||||||
*
|
|
||||||
* Note that if there are many elements that are equal to [value] using the [compare],
|
|
||||||
* it will return index of some of it. Use [findFirst] and [findLast] if needed.
|
|
||||||
*
|
|
||||||
* @return found value or null
|
|
||||||
*/
|
|
||||||
fun find(value: T): T? {
|
|
||||||
val i = binarySearch(value)
|
|
||||||
return if( i < 0 ) null else list[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find all elements equal to the value.
|
|
||||||
* @return list of found elements, or an empty list.
|
|
||||||
*/
|
|
||||||
fun findAll(value: T): List<T> {
|
|
||||||
val result = mutableListOf<T>()
|
|
||||||
val start = binarySearch(value)
|
|
||||||
if( start >= 0) {
|
|
||||||
for( i in start ..< size ) {
|
|
||||||
val element = list[i]
|
|
||||||
if( compare(value, element) == 0 )
|
|
||||||
result += element
|
|
||||||
else
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if( start > 0) {
|
|
||||||
for( i in (start-1) downTo 0) {
|
|
||||||
val element = list[i]
|
|
||||||
if (compare(value, element) == 0)
|
|
||||||
result += element
|
|
||||||
else
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add all values. Duplicates will also be added.
|
|
||||||
*/
|
|
||||||
fun add(vararg values: T) {
|
|
||||||
for( value in values ) {
|
|
||||||
val i = binarySearch(value)
|
|
||||||
if (i >= 0)
|
|
||||||
list.add(i + 1, value)
|
|
||||||
else
|
|
||||||
list.add(-(i + 1), value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove one element equals to value.
|
|
||||||
* @return true if the element has been removed
|
|
||||||
*/
|
|
||||||
fun remove(value: T): Boolean {
|
|
||||||
val i = binarySearch(value)
|
|
||||||
return if( i >= 0) {
|
|
||||||
list.removeAt(i)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove element at index.
|
|
||||||
* @return element that has been removed
|
|
||||||
*/
|
|
||||||
@Suppress("unused")
|
|
||||||
fun removeAt(index: Int): T = list.removeAt(index)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optimized, binary search based version.
|
|
||||||
* @returns index of the _first_ occurrence of the element, or -1
|
|
||||||
*/
|
|
||||||
override fun indexOf(element: T): Int {
|
|
||||||
var i = binarySearch(element)
|
|
||||||
if( i < 0 ) return -1
|
|
||||||
while( i > 0 && compare(element, list[i-1]) == 0) i--
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optimized, binary search based version.
|
|
||||||
* @returns index of the _last_ occurrence of the element, or -1
|
|
||||||
*/
|
|
||||||
override fun lastIndexOf(element: T): Int {
|
|
||||||
var i = binarySearch(element)
|
|
||||||
if( i < 0 ) return -1
|
|
||||||
while( i < list.size && compare(element, list[i+1])==0) i++
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optimized, binary search based, first occurrence of the element.
|
|
||||||
*
|
|
||||||
* Note that the order of 'equal' elements is unspecified, order of appearance is not kept.
|
|
||||||
*/
|
|
||||||
fun findFirst(element: T): T? {
|
|
||||||
val i = indexOf(element)
|
|
||||||
return if( i < 0 ) null else list[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optimized, binary search based search of the last occurrence of the element
|
|
||||||
*
|
|
||||||
* Note that the order of 'equal' elements is unspecified, order of appearance is not kept.
|
|
||||||
*/
|
|
||||||
fun findLast(element: T): T? {
|
|
||||||
val i = lastIndexOf(element)
|
|
||||||
return if( i < 0 ) null else list[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all elements equals to value.
|
|
||||||
* @return number of removed elements
|
|
||||||
*/
|
|
||||||
fun removeAll(value: T): Int {
|
|
||||||
var start = binarySearch(value)
|
|
||||||
var count = 0
|
|
||||||
while( start < size && compare(value, list[start]) == 0 ) {
|
|
||||||
list.removeAt(start)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
while( start > 0 && compare(value, list[--start]) == 0) {
|
|
||||||
list.removeAt(start)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
override operator fun contains(element: T): Boolean = binarySearch(element) >= 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a value if it is not yet in the list.
|
|
||||||
* @return true if the value was added and false if it is already in the list, and was not added.
|
|
||||||
*/
|
|
||||||
fun addIfNotExists(value: T): Boolean {
|
|
||||||
val i = binarySearch(value)
|
|
||||||
return if( i < 0) {
|
|
||||||
list.add(-(i + 1), value)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else false
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Construct list of elements from comparable instances.
|
|
||||||
*/
|
|
||||||
operator fun <T: Comparable<T>>invoke(vararg values: T): SortedList<T> =
|
|
||||||
SortedList(values.toList().sorted().toMutableList()) { a, b -> a.compareTo(b) }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -30,7 +30,6 @@ data class Foobar2(val bar: Int, val foo: Int, val other: Int = -1)
|
|||||||
@Framed
|
@Framed
|
||||||
data class FoobarF1(val bar: Int, val foo: Int = 117)
|
data class FoobarF1(val bar: Int, val foo: Int = 117)
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@Framed
|
@Framed
|
||||||
@SerialName("bipack.FoobarF1")
|
@SerialName("bipack.FoobarF1")
|
||||||
@ -441,21 +440,4 @@ class BipackEncoderTest {
|
|||||||
println(y)
|
println(y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class T1(@Fixed val i: Byte)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testFixedByte() {
|
|
||||||
fun t1(i: Int) {
|
|
||||||
val packed = BipackEncoder.encode(T1(i.toByte()))
|
|
||||||
println(packed.toDump())
|
|
||||||
assertEquals(1, packed.size)
|
|
||||||
assertEquals(i, BipackDecoder.decode<T1>(packed).i.toInt())
|
|
||||||
}
|
|
||||||
t1(127)
|
|
||||||
t1(-127)
|
|
||||||
t1(1)
|
|
||||||
t1(-1)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,60 +0,0 @@
|
|||||||
package bipack
|
|
||||||
|
|
||||||
import net.sergeych.bintools.*
|
|
||||||
import net.sergeych.collections.bitSetOf
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertFalse
|
|
||||||
import kotlin.test.assertNull
|
|
||||||
|
|
||||||
class StorageTest {
|
|
||||||
@Test
|
|
||||||
fun storageTest3() {
|
|
||||||
val s1 = MemoryKVStorage()
|
|
||||||
|
|
||||||
val s2 = defaultNamedStorage("test_mp_bintools2")
|
|
||||||
s2.clear()
|
|
||||||
|
|
||||||
s1.write("foo", "bar")
|
|
||||||
s1.write("foo2", "bar2")
|
|
||||||
|
|
||||||
s2.write("foo", "foobar")
|
|
||||||
s2.write("bar", "buzz")
|
|
||||||
s2.write("reason", 42)
|
|
||||||
|
|
||||||
assertEquals("bar", s1.read("foo"))
|
|
||||||
assertEquals("bar2", s1.read("foo2"))
|
|
||||||
assertNull(s1.get("bar"))
|
|
||||||
|
|
||||||
val reason: Int? by s1.optStored()
|
|
||||||
assertNull(s1.get("reason"))
|
|
||||||
assertNull(reason)
|
|
||||||
|
|
||||||
s1.connectToStorage(s2)
|
|
||||||
|
|
||||||
// s1 overwrites s2!
|
|
||||||
assertEquals("bar", s1.read("foo"))
|
|
||||||
|
|
||||||
// don't change
|
|
||||||
assertEquals("bar2", s1.read("foo2"))
|
|
||||||
|
|
||||||
// pull from s1
|
|
||||||
assertEquals("buzz", s1.read("bar"))
|
|
||||||
|
|
||||||
assertEquals(42, s1.read("reason"))
|
|
||||||
assertEquals(42, reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun bitSetEquityTest() {
|
|
||||||
val a = bitSetOf(1, 12)
|
|
||||||
val b = bitSetOf(12, 1)
|
|
||||||
assertEquals(a, b)
|
|
||||||
assertEquals(b, a)
|
|
||||||
a += 1230
|
|
||||||
assertFalse { a == b }
|
|
||||||
a -= 1230
|
|
||||||
assertEquals(a, b)
|
|
||||||
assertEquals(b, a)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,166 +0,0 @@
|
|||||||
package collections
|
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.test.advanceTimeBy
|
|
||||||
import kotlinx.coroutines.test.resetMain
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import kotlinx.coroutines.test.setMain
|
|
||||||
import net.sergeych.bintools.toDump
|
|
||||||
import net.sergeych.bipack.BipackEncoder
|
|
||||||
import net.sergeych.collections.*
|
|
||||||
import kotlin.test.*
|
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
|
|
||||||
class CollectionsTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSortedList1() {
|
|
||||||
val a1 = SortedList(5, 4, 3, 9, 1)
|
|
||||||
assertTrue { 4 in a1 }
|
|
||||||
assertTrue { 14 !in a1 }
|
|
||||||
fun <T : Comparable<T>> test(x: SortedList<T>) {
|
|
||||||
var last: T? = null
|
|
||||||
for (i in x.toList()) {
|
|
||||||
if (last == null) last = i
|
|
||||||
else if (last > i) fail("invalid order: $last should be <= $i")
|
|
||||||
assertContains(x, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
test(a1)
|
|
||||||
a1.add(11, 3, 2, 1, 0, 9, 22, -55, 0, 1, 0)
|
|
||||||
println(a1.toList())
|
|
||||||
assertEquals(listOf(-55, 0, 0, 0, 1, 1, 1, 2, 3, 3, 4, 5, 9, 9, 11, 22), a1.toList())
|
|
||||||
assertEquals(11, a1.find(11))
|
|
||||||
assertEquals(listOf(0, 0, 0), a1.findAll(0))
|
|
||||||
assertEquals(listOf(11), a1.findAll(11))
|
|
||||||
assertEquals(listOf(3, 3), a1.findAll(3))
|
|
||||||
assertTrue { a1.remove(3) }
|
|
||||||
assertEquals(listOf(3), a1.findAll(3))
|
|
||||||
assertTrue { a1.remove(3) }
|
|
||||||
assertEquals(listOf(), a1.findAll(3))
|
|
||||||
assertTrue { 3 !in a1 }
|
|
||||||
assertEquals(3, a1.findAll(1).size)
|
|
||||||
assertEquals(3, a1.removeAll(1))
|
|
||||||
assertTrue { 1 !in a1 }
|
|
||||||
assertEquals(listOf(-55, 0, 0, 0, 2, 4, 5, 9, 9, 11, 22), a1.toList())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun expirableAsyncCacheTest() = runTest {
|
|
||||||
val removedValues = mutableSetOf<Int>()
|
|
||||||
val m = ExpirableAsyncCache<String, Int>(500.milliseconds) {
|
|
||||||
removedValues += it
|
|
||||||
}
|
|
||||||
m.put("one", 1)
|
|
||||||
m.put("two", 2)
|
|
||||||
assertTrue("one" in m)
|
|
||||||
assertTrue("two" in m)
|
|
||||||
assertEquals(1, m.get("one"))
|
|
||||||
assertEquals(2, m.get("two"))
|
|
||||||
assertTrue { removedValues.isEmpty() }
|
|
||||||
|
|
||||||
m.put("one", 11)
|
|
||||||
assertEquals(11, m.get("one"))
|
|
||||||
assertEquals(removedValues, setOf(1))
|
|
||||||
|
|
||||||
m.getOrDefault("two", 22)
|
|
||||||
assertEquals(2, m.get("two"))
|
|
||||||
|
|
||||||
m.getOrPut("two") { 222 }
|
|
||||||
assertEquals(2, m.get("two"))
|
|
||||||
|
|
||||||
m.getOrPut("three") { 3 }
|
|
||||||
assertEquals(3, m.get("three"))
|
|
||||||
// This sadly is not working
|
|
||||||
// delay(2000)
|
|
||||||
// advanceTimeBy(2000)
|
|
||||||
// delay(1000)
|
|
||||||
// assertNull(m.get("one"))
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class Nn {
|
|
||||||
One, Two, Three
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun bitsetTest() {
|
|
||||||
fun checkAdd(vararg values: Int) {
|
|
||||||
val x = bitSetOf(*values)
|
|
||||||
println(":: ${values.toList()}: ${x.toHex()}")
|
|
||||||
assertEquals(values.toSet(), x.toIntSet())
|
|
||||||
for (i in values) {
|
|
||||||
assertTrue(i in x, "failed $i in ${values.toList()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkAdd(0, 1, 2, 3)
|
|
||||||
val src = intArrayOf(31, 32, 33, 60, 61, 62, 63, 64, 65)
|
|
||||||
checkAdd(*src)
|
|
||||||
|
|
||||||
assertEquals(src.toSet(), src.toBitSet().toIntSet())
|
|
||||||
assertFalse { src.toSet() != src.toBitSet() }
|
|
||||||
assertFalse { src.toSet() + 17 == src.toBitSet() }
|
|
||||||
assertEquals(src.toBitSet() + 17, src.toSet() + 17, )
|
|
||||||
assertTrue { src.toSet() + 17 == src.toBitSet() + 17 }
|
|
||||||
assertTrue { src.toBitSet() + 17 == src.toBitSet() + 17 }
|
|
||||||
|
|
||||||
var y = src.toBitSet() + 2
|
|
||||||
assertTrue { y.retainAll(setOf(1, 3, 31, 32, 33)) }
|
|
||||||
assertEquals(setOf(31, 32, 33), y.toIntSet())
|
|
||||||
assertFalse { y.retainAll(setOf(1, 3, 31, 32, 33)) }
|
|
||||||
|
|
||||||
y = src.toBitSet() + 2
|
|
||||||
for (i in setOf(2, 31, 32, 33))
|
|
||||||
assertTrue(i in y, "failed $i in ${y.toList()}")
|
|
||||||
assertTrue { y.retainAll(bitSetOf(1, 3, 31, 32, 33)) }
|
|
||||||
assertEquals(setOf(31, 32, 33), y.toIntSet())
|
|
||||||
assertFalse { y.retainAll(setOf(1, 3, 31, 32, 33)) }
|
|
||||||
|
|
||||||
var z = src.toBitSet() + 2
|
|
||||||
assertTrue { z.removeAll(setOf(31, 65)) }
|
|
||||||
assertEquals(listOf(2, 32, 33, 60, 61, 62, 63, 64), z.toList())
|
|
||||||
assertFalse { z.removeAll(setOf(31, 65)) }
|
|
||||||
|
|
||||||
z = src.toBitSet() + 2
|
|
||||||
assertTrue { z.removeAll(bitSetOf(31, 65)) }
|
|
||||||
assertEquals(listOf(2, 32, 33, 60, 61, 62, 63, 64), z.toList())
|
|
||||||
assertFalse { z.removeAll(setOf(31, 65)) }
|
|
||||||
|
|
||||||
z = src.toBitSet() + 2
|
|
||||||
assertTrue { z.removeAll(bitSetOf(31, 32)) }
|
|
||||||
assertEquals(listOf(2, 33, 60, 61, 62, 63, 64, 65), z.toList())
|
|
||||||
assertFalse { z.removeAll(setOf(31, 4)) }
|
|
||||||
|
|
||||||
assertTrue {
|
|
||||||
BipackEncoder.encode(src.toSet()).size > BipackEncoder.encode(src.toBitSet()).size
|
|
||||||
}
|
|
||||||
|
|
||||||
assertFalse { z includes Nn.Two }
|
|
||||||
assertTrue { z includes Nn.Three }
|
|
||||||
assertTrue { z + 1 includesAll listOf(Nn.Three, Nn.Two) }
|
|
||||||
|
|
||||||
assertEquals(setOf(Nn.One, Nn.Three), bitSetOf(0, 2).toEnumSet())
|
|
||||||
assertTrue { z + 1 includesAny setOf(Nn.One, Nn.Two) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun bitsetEnumsTest() {
|
|
||||||
val a = bitSetOfEnum(Nn.One)
|
|
||||||
assertTrue { a includes Nn.One }
|
|
||||||
assertFalse { a includes Nn.Two }
|
|
||||||
assertFalse { a includes Nn.Three }
|
|
||||||
a.insert(Nn.Three)
|
|
||||||
assertEquals(setOf(Nn.One, Nn.Three), a.toEnumSet())
|
|
||||||
a.delete(Nn.One)
|
|
||||||
assertEquals(setOf(Nn.Three), a.toEnumSet())
|
|
||||||
a.insertAll(listOf(Nn.Two, Nn.Three))
|
|
||||||
assertEquals(setOf(Nn.Two, Nn.Three), a.toEnumSet())
|
|
||||||
assertTrue { a includesAll listOf(Nn.Two, Nn.Three) }
|
|
||||||
assertFalse { a includesAll listOf(Nn.One, Nn.Two) }
|
|
||||||
assertTrue { a includesAny listOf(Nn.One, Nn.Two) }
|
|
||||||
a.deleteAll(listOf(Nn.Two, Nn.Three, Nn.One))
|
|
||||||
assertTrue { a.isEmpty() }
|
|
||||||
assertTrue { a.toEnumSet<Nn>().isEmpty() }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package net.sergeych.synctools
|
|
||||||
|
|
||||||
import java.nio.channels.CompletionHandler
|
|
||||||
import kotlin.coroutines.Continuation
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.resumeWithException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to handle Java continuation with Kotlin coroutines.
|
|
||||||
* Usage sample:
|
|
||||||
* ```kotlin
|
|
||||||
* val socket = withContext(Dispatchers.IO) {
|
|
||||||
* AsynchronousSocketChannel.open()
|
|
||||||
* }
|
|
||||||
* suspendCoroutine { cont ->
|
|
||||||
* socket.connect(address.socketAddress, cont, VoidCompletionHandler)
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
open class ContinuationHandler<T> : CompletionHandler<T, Continuation<T>> {
|
|
||||||
override fun completed(result: T, attachment: Continuation<T>) {
|
|
||||||
attachment.resume(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun failed(exc: Throwable, attachment: Continuation<T>) {
|
|
||||||
attachment.resumeWithException(exc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object VoidCompletionHandler : ContinuationHandler<Void>()
|
|
||||||
|
|
||||||
object IntCompletionHandler : ContinuationHandler<Int>()
|
|
9
src/wasmJsTest/kotlin/EmptyTest.kt
Normal file
9
src/wasmJsTest/kotlin/EmptyTest.kt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class EmptyTest {
|
||||||
|
@Test
|
||||||
|
fun emptyTest() {
|
||||||
|
println("hello!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,6 +5,10 @@ import kotlin.test.assertNull
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class StorageTest {
|
class StorageTest {
|
||||||
|
@Test
|
||||||
|
fun emptyTest() {
|
||||||
|
println("hello")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun storageTest() {
|
fun storageTest() {
|
||||||
@ -23,6 +27,7 @@ class StorageTest {
|
|||||||
assertEquals(answer, 42)
|
assertEquals(answer, 42)
|
||||||
answer = 43
|
answer = 43
|
||||||
|
|
||||||
|
println("----------------------------------------------------------------")
|
||||||
val s2 = defaultNamedStorage("test_mp_bintools")
|
val s2 = defaultNamedStorage("test_mp_bintools")
|
||||||
val foo1 by s2.stored("?", "foo")
|
val foo1 by s2.stored("?", "foo")
|
||||||
val answer1: Int? by s2.optStored("answer")
|
val answer1: Int? by s2.optStored("answer")
|
||||||
@ -34,4 +39,5 @@ class StorageTest {
|
|||||||
s2.write("test_$i", "payload_$i")
|
s2.write("test_$i", "payload_$i")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user