├── .github
└── workflows
│ ├── build.yml
│ ├── deploy.yml
│ └── release.yml
├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
└── inspectionProfiles
│ ├── ktlint.xml
│ └── profiles_settings.xml
├── .yarnrc
├── LICENSE.txt
├── README.md
├── RELEASING.md
├── build.gradle.kts
├── build.gradle.old
├── convention-plugins
├── build.gradle.kts
├── settings.gradle.kts
└── src
│ └── main
│ └── kotlin
│ └── kmp-setup.gradle.kts
├── deprecated
├── README.md
├── stately-collections
│ ├── build.gradle
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── co
│ │ │ └── touchlab
│ │ │ └── stately
│ │ │ └── collections
│ │ │ ├── Functions.kt
│ │ │ ├── ObjectPool.kt
│ │ │ ├── SharedHashMap.kt
│ │ │ ├── SharedLinkedList.kt
│ │ │ ├── SharedLruCache.kt
│ │ │ └── SharedSet.kt
│ │ ├── commonTest
│ │ └── kotlin
│ │ │ └── co
│ │ │ └── touchlab
│ │ │ └── stately
│ │ │ └── collections
│ │ │ ├── JsIgnore.kt
│ │ │ ├── ObjectPoolTest.kt
│ │ │ ├── SharedHashMapTest.kt
│ │ │ ├── SharedLinkedListTest.kt
│ │ │ ├── SharedLruCacheTest.kt
│ │ │ ├── SharedSetTest.kt
│ │ │ └── StableReadListTest.kt
│ │ ├── jsAndWasmJsMain
│ │ └── kotlin
│ │ │ └── co
│ │ │ └── touchlab
│ │ │ └── stately
│ │ │ └── collections
│ │ │ └── Functions.kt
│ │ ├── jsAndWasmJsTest
│ │ └── kotlin
│ │ │ └── co
│ │ │ └── touchlab
│ │ │ └── stately
│ │ │ └── collections
│ │ │ └── JsIgnore.kt
│ │ ├── jvmMain
│ │ └── kotlin
│ │ │ └── co
│ │ │ └── touchlab
│ │ │ └── stately
│ │ │ └── collections
│ │ │ └── FunctionsJVM.kt
│ │ └── nativeMain
│ │ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── collections
│ │ ├── CopyOnWriteList.kt
│ │ └── Functions.kt
├── stately-common
│ ├── build.gradle
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── co
│ │ │ └── touchlab
│ │ │ └── stately
│ │ │ └── Helpers.kt
│ │ ├── commonTest
│ │ └── kotlin
│ │ │ └── co
│ │ │ └── touchlab
│ │ │ └── stately
│ │ │ └── HelpersTest.kt
│ │ ├── jsAndWasmJsMain
│ │ └── kotlin
│ │ │ └── co
│ │ │ └── touchlab
│ │ │ └── stately
│ │ │ └── HelpersJS.kt
│ │ ├── jvmMain
│ │ └── kotlin
│ │ │ └── co
│ │ │ └── touchlab
│ │ │ └── stately
│ │ │ └── HelpersJVM.kt
│ │ └── nativeMain
│ │ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── Helpers.kt
├── stately-iso-collections
│ ├── build.gradle
│ ├── karma.config.d
│ │ └── timeout-override.js
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── co
│ │ │ └── touchlab
│ │ │ └── stately
│ │ │ └── collections
│ │ │ ├── Functions.kt
│ │ │ ├── IsoArrayDeque.kt
│ │ │ ├── IsoMutableCollection.kt
│ │ │ ├── IsoMutableIterator.kt
│ │ │ ├── IsoMutableList.kt
│ │ │ ├── IsoMutableMap.kt
│ │ │ └── IsoMutableSet.kt
│ │ ├── commonTest
│ │ └── kotlin
│ │ │ └── co
│ │ │ └── touchlab
│ │ │ └── stately
│ │ │ └── collections
│ │ │ ├── ExtendingCollections.kt
│ │ │ ├── IsoArrayDequeTest.kt
│ │ │ ├── IsoMutableListTest.kt
│ │ │ ├── IsoMutableMapTest.kt
│ │ │ ├── IsoMutableSetTest.kt
│ │ │ └── JsIgnore.kt
│ │ └── jsAndWasmJsTest
│ │ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── collections
│ │ └── JsIgnore.kt
└── stately-isolate
│ ├── build.gradle
│ └── src
│ ├── commonMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── isolate
│ │ ├── IsoState.kt
│ │ └── StateRunner.kt
│ ├── commonTest
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── isolate
│ │ └── IsoStateTest.kt
│ ├── jsAndWasmJsMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── isolate
│ │ ├── BackgroundStateRunner.kt
│ │ └── StateHolder.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── isolate
│ │ ├── BackgroundStateRunner.kt
│ │ └── StateHolder.kt
│ └── nativeMain
│ └── kotlin
│ └── co
│ └── touchlab
│ └── stately
│ └── isolate
│ ├── BackgroundStateRunner.kt
│ └── Platform.kt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── kotlin-js-store
└── yarn.lock
├── settings.gradle.kts
├── stately-concurrency
├── api
│ └── stately-concurrency.api
├── build.gradle.kts
└── src
│ ├── AndroidNativeMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── concurrency
│ │ └── Lock.kt
│ ├── appleMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── concurrency
│ │ └── Lock.kt
│ ├── commonMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── concurrency
│ │ ├── AtomicBoolean.kt
│ │ ├── AtomicInt.kt
│ │ ├── AtomicLong.kt
│ │ ├── AtomicReference.kt
│ │ ├── Functions.kt
│ │ ├── Lock.kt
│ │ ├── ThreadLocal.kt
│ │ └── ThreadRef.kt
│ ├── commonTest
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── concurrency
│ │ ├── Atomics.kt
│ │ ├── LockTest.kt
│ │ └── ThreadRefTest.kt
│ ├── jsAndWasmJsMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── concurrency
│ │ ├── AtomicInt.kt
│ │ ├── AtomicLong.kt
│ │ ├── AtomicReference.kt
│ │ ├── Lock.kt
│ │ ├── Synchronizable.kt
│ │ ├── ThreadLocal.kt
│ │ └── ThreadRef.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── concurrency
│ │ ├── AtomicInt.kt
│ │ ├── AtomicLong.kt
│ │ ├── AtomicReference.kt
│ │ ├── Lock.kt
│ │ ├── Synchronizable.kt
│ │ ├── ThreadLocalJVM.kt
│ │ └── ThreadRef.kt
│ ├── linuxMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── concurrency
│ │ └── Lock.kt
│ ├── mingwMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── concurrency
│ │ └── Lock.kt
│ ├── nativeMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── concurrency
│ │ ├── AtomicInt.kt
│ │ ├── AtomicLong.kt
│ │ ├── AtomicReference.kt
│ │ ├── Functions.kt
│ │ ├── GuardedStableRef.kt
│ │ ├── ThreadLocal.kt
│ │ └── ThreadRef.kt
│ └── nativeTest
│ └── kotlin
│ └── co
│ └── touchlab
│ └── stately
│ └── concurrency
│ └── GuardedStableRefTest.kt
├── stately-concurrent-collections
├── api
│ └── stately-concurrent-collections.api
├── build.gradle.kts
└── src
│ ├── commonMain
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── collections
│ │ ├── ConcurrentMutableCollection.kt
│ │ ├── ConcurrentMutableList.kt
│ │ ├── ConcurrentMutableMap.kt
│ │ └── ConcurrentMutableSet.kt
│ ├── commonTest
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── collections
│ │ ├── ConcurrentMutableCollectionTest.kt
│ │ ├── ConcurrentMutableListTest.kt
│ │ ├── ConcurrentMutableMapTest.kt
│ │ └── TestHelpers.kt
│ ├── jsAndWasmJsTest
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── collections
│ │ └── backgroundDispatcher.kt
│ ├── jvmTest
│ └── kotlin
│ │ └── co
│ │ └── touchlab
│ │ └── stately
│ │ └── collections
│ │ └── backgroundDispatcher.kt
│ └── nativeTest
│ └── kotlin
│ └── co
│ └── touchlab
│ └── stately
│ └── collections
│ └── backgroundDispatcher.kt
└── stately-strict
├── api
└── stately-strict.api
├── build.gradle.kts
└── src
├── commonMain
└── kotlin
│ └── co
│ └── touchlab
│ └── stately
│ └── strict
│ └── Helpers.kt
├── jsAndWasmJsMain
└── kotlin
│ └── co
│ └── touchlab
│ └── stately
│ └── strict
│ └── HelpersJS.kt
├── jvmMain
└── kotlin
│ └── co
│ └── touchlab
│ └── stately
│ └── strict
│ └── HelpersJVM.kt
├── nativeMain
└── kotlin
│ └── co
│ └── touchlab
│ └── stately
│ └── strict
│ └── Helpers.kt
└── nativeTest
└── kotlin
└── co
└── touchlab
└── stately
└── strict
└── HelpersTest.kt
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | on:
3 | pull_request:
4 | push:
5 | branches:
6 | - main
7 | workflow_dispatch:
8 |
9 | jobs:
10 | deploy:
11 | runs-on: macos-latest
12 | steps:
13 | - name: Checkout the repo
14 | uses: actions/checkout@v3
15 |
16 | - uses: actions/setup-java@v2
17 | with:
18 | distribution: "adopt"
19 | java-version: "17"
20 | - name: Validate Gradle Wrapper
21 | uses: gradle/wrapper-validation-action@v1
22 | - name: Cache gradle
23 | uses: actions/cache@v2
24 | with:
25 | path: ~/.gradle/caches
26 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
27 | restore-keys: |
28 | ${{ runner.os }}-gradle-
29 |
30 | - name: Cache konan
31 | uses: actions/cache@v2
32 | with:
33 | path: ~/.konan
34 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
35 | restore-keys: |
36 | ${{ runner.os }}-gradle-
37 |
38 | - name: Run Gradle Build
39 | run: ./gradlew build --no-daemon --stacktrace --no-build-cache
40 | env:
41 | ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
42 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
43 | ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
44 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
45 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
46 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
47 |
48 | - name: Upload Stately Collections Build Results
49 | if: always()
50 | uses: actions/upload-artifact@v2
51 | with:
52 | name: deprecated-test-results
53 | path: deprecated/**/build/reports/tests/
54 | env:
55 | GRADLE_OPTS: -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m"
56 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: deploy
2 | on: workflow_dispatch
3 |
4 | jobs:
5 | deploy:
6 | runs-on: macos-latest
7 | steps:
8 | - name: Checkout the repo
9 | uses: actions/checkout@v3
10 |
11 | - uses: actions/setup-java@v2
12 | with:
13 | distribution: "adopt"
14 | java-version: "17"
15 | - name: Validate Gradle Wrapper
16 | uses: gradle/wrapper-validation-action@v1
17 | - name: Cache gradle
18 | uses: actions/cache@v2
19 | with:
20 | path: ~/.gradle/caches
21 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
22 | restore-keys: |
23 | ${{ runner.os }}-gradle-
24 |
25 | - name: Cache konan
26 | uses: actions/cache@v2
27 | with:
28 | path: ~/.konan
29 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
30 | restore-keys: |
31 | ${{ runner.os }}-gradle-
32 |
33 | - name: Publish Mac/Windows Artifacts
34 | run: ./gradlew publish --no-daemon --stacktrace --no-build-cache
35 | env:
36 | ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
37 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
38 | ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
39 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
40 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
41 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
42 |
43 | env:
44 | GRADLE_OPTS: -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m"
45 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 | on: workflow_dispatch
3 |
4 | jobs:
5 | release:
6 | runs-on: macos-latest
7 | steps:
8 | - name: Checkout the repo
9 | uses: actions/checkout@v3
10 |
11 | - uses: touchlab/read-property@0.1
12 | id: version-name
13 | with:
14 | file: ./gradle.properties
15 | property: VERSION_NAME
16 |
17 | - name: Echo Version
18 | run: echo "${{ steps.version-name.outputs.propVal }}"
19 |
20 | - uses: actions/setup-java@v2
21 | with:
22 | distribution: "adopt"
23 | java-version: "17"
24 | - name: Validate Gradle Wrapper
25 | uses: gradle/wrapper-validation-action@v1
26 | - name: Cache gradle
27 | uses: actions/cache@v2
28 | with:
29 | path: ~/.gradle/caches
30 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
31 | restore-keys: |
32 | ${{ runner.os }}-gradle-
33 |
34 | - name: Cache konan
35 | uses: actions/cache@v2
36 | with:
37 | path: ~/.konan
38 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
39 | restore-keys: |
40 | ${{ runner.os }}-gradle-
41 |
42 | - name: Finish Maven Central Release
43 | run: ./gradlew closeAndReleaseRepository --no-daemon --stacktrace --no-build-cache
44 | env:
45 | ORG_GRADLE_PROJECT_SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
46 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }}
47 | ORG_GRADLE_PROJECT_SONATYPE_NEXUS_PASSWORD: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
48 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }}
49 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
50 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
51 |
52 | - name: Create Release
53 | if: ${{ contains(steps.version-match.outputs.group1, 'SNAPSHOT') == false }}
54 | uses: touchlab/release-action@v1.10.0
55 | with:
56 | tag: ${{ steps.version-name.outputs.propVal }}
57 |
58 | env:
59 | GRADLE_OPTS: -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:MaxMetaspaceSize=512m"
60 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle files
2 | .gradle/
3 | build/
4 | local.properties
5 |
6 | # Idea
7 | *.iws
8 | *.ipr
9 | *.iml
10 | .idea/**/*
11 | !.idea/inspectionProfiles/
12 | !.idea/inspectionProfiles/**
13 | !.idea/codeStyles/
14 | !.idea/codeStyles/**
15 |
16 | # Built application files
17 | *.apk
18 | *.ap_
19 |
20 | # Files for the ART/Dalvik VM
21 | *.dex
22 |
23 | # Java class files
24 | *.class
25 | # Keystore files
26 | *.jks
27 |
28 | # Generated files
29 | bin/
30 | gen/
31 | out/
32 |
33 | #Because ugh
34 | .DS_Store
35 |
36 | .vscode
37 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/ktlint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | --ignore-engines true
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Stately
2 |
3 | Stately is a state utility library to facilitate state management in Kotlin Multiplatform. It was originally written to facilitate development with the strict Kotlin/Native memory model. As of Kotlin 1.7.20, the strict model is deprecated, and the [releveant modules of Stately have also been deprecated](deprecated) but are still published and available.
4 |
5 | Stately currently provides concurrencly primitives and concurrent collections.
6 |
7 | ## stately-concurrency
8 |
9 | `stately-concurrency` includes some concurrency support classes. These include a set of `Atomicxxx` classes, a `Lock`, a `ThreadLocal` container, a `Synchronizable` type, and a class `ThreadRef` that allows you to hold a thread id.
10 |
11 | Much of the functionality of this module is similar to [atomic-fu](https://github.com/Kotlin/kotlinx.atomicfu). They differ in some ways, so while they both cover much of the same ground, Stately's version still has some use.
12 |
13 | `ThreadRef` is unique to Stately. It allows you to capture a reference to the id of the thread in which it was created, and ask if the current thread is the same. Just FYI, it does *not* keep a reference to the actual thread. Just an id. Usage looks like this:
14 |
15 | ```kotlin
16 | fun useRef(){
17 | val threadRef = ThreadRef()
18 | threadRef.same() // <- true
19 | backgroundThread {
20 | threadRef.same() // <- false
21 | }
22 | }
23 | ```
24 |
25 | The `Synchronizable` type allows us to use the JVM's `synchronized` but in common, and with Kotlin/Native which doesn't natively support it.
26 |
27 | ```kotlin
28 | class MyMutableData(private var count: Int = 0) : Synchronizable() {
29 | fun add() {
30 | synchronize { count++ }
31 | }
32 |
33 | val myCount: Int
34 | get() = synchronize { count }
35 | }
36 | ```
37 |
38 | Your type should extend `Synchronizable`, which on the JVM is typealiased to `Any`. Then you can use `synchronize` as in the example above.
39 |
40 | ### Config
41 |
42 | ```groovy
43 | commonMain {
44 | dependencies {
45 | implementation("co.touchlab:stately-concurrency:2.0.0")
46 | }
47 | }
48 | ```
49 |
50 | ## stately-concurrent-collections
51 |
52 | A set of relatively simple mutable collections that are thread safe.
53 |
54 | ```kotlin
55 | val list = ConcurrentMutableList()
56 | val list.add(42)
57 | ```
58 |
59 | ### Config
60 |
61 | ```groovy
62 | commonMain {
63 | dependencies {
64 | implementation("co.touchlab:stately-concurrent-collections:2.0.0")
65 | }
66 | }
67 | ```
68 |
69 | > ## Subscribe!
70 | >
71 | > We build solutions that get teams started smoothly with Kotlin Multiplatform Mobile and ensure their success in production. Join our community to learn how your peers are adopting KMM.
72 | [Sign up here](https://go.touchlab.co/newsletter-gh)!
73 |
74 | ## Primary Maintainer
75 |
76 | [Kevin Galligan](https://github.com/kpgalligan/)
77 |
78 | 
79 |
80 | *Ping me on twitter [@kpgalligan](https://twitter.com/kpgalligan/) if you don't get a timely reply!* -Kevin
81 |
82 | License
83 | =======
84 |
85 | Copyright 2024 Touchlab, Inc.
86 |
87 | Licensed under the Apache License, Version 2.0 (the "License");
88 | you may not use this file except in compliance with the License.
89 | You may obtain a copy of the License at
90 |
91 | http://www.apache.org/licenses/LICENSE-2.0
92 |
93 | Unless required by applicable law or agreed to in writing, software
94 | distributed under the License is distributed on an "AS IS" BASIS,
95 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
96 | See the License for the specific language governing permissions and
97 | limitations under the License.
98 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | # Releasing
2 |
3 | In order to be able to release, you'll need an account at [https://oss.sonatype.org/](https://oss.sonatype.org/), and admin access to the repo.
4 |
5 | To create an account, ask around the slack (if you're not working at Touchlab, this page isn't for you anyway. Sorry :( )
6 |
7 | Steps:
8 |
9 | 1. Make sure any PR's are applied and the release is ready to be published.
10 | 2. Switch to the branch you'll release from, and `git pull` the latest.
11 | 3. Change the version in `gradle.properties`.
12 | 4. Git add/commit with message "Version _._._"
13 | 5. Push that change (directly to the branch, no PR).
14 | 6. Open Actions, select the `deploy` workflow on the left.
15 | 7. Select `Run Workflow` on the right, pick the branch, and click `Run Workflow`.
16 | 8. When complete, log into [https://oss.sonatype.org/](https://oss.sonatype.org/).
17 | 9. Select `Staging Profiles` on the left.
18 | 10. Visually review the pushed artifacts (briefly). Just get a sense that it looks correct.
19 | 11. Select all and click `Close`.
20 | 12. Hit the Refresh button periodically. Not the browser refresh. There's a button on the page called "Refresh".
21 | 13. It'll take a while. Eventually `Release` will be enabled. Click that.
22 | 14. To check status, look for the library in [https://repo1.maven.org/maven2/co/touchlab/](https://repo1.maven.org/maven2/co/touchlab/), find an artifact, and refresh until the version you just published shows up.
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.kotlin.multiplatform) apply false
3 | alias(libs.plugins.android.library) apply false
4 | alias(libs.plugins.buildconfig) apply false
5 | alias(libs.plugins.binaryCompatibilityValidator)
6 | alias(libs.plugins.dokka) apply false
7 | alias(libs.plugins.touchlab.docusaurusosstemplate)
8 | alias(libs.plugins.mavenPublish) apply false
9 | }
10 |
11 | apiValidation {
12 | nonPublicMarkers.add("co.touchlab.kermit.ExperimentalKermitApi")
13 | ignoredProjects.addAll(
14 | listOf(
15 | "stately-collections",
16 | "stately-common",
17 | "stately-iso-collections",
18 | "stately-isolate"
19 | )
20 | )
21 | }
22 |
23 | val GROUP: String by project
24 | val VERSION_NAME: String by project
25 |
26 | allprojects {
27 | group = GROUP
28 | version = VERSION_NAME
29 | }
--------------------------------------------------------------------------------
/build.gradle.old:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | buildscript {
18 | repositories {
19 | mavenLocal()
20 | jcenter()
21 | google()
22 | gradlePluginPortal()
23 | }
24 |
25 | dependencies {
26 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
27 | classpath 'com.android.tools.build:gradle:3.5.1'
28 | classpath "org.jlleitschuh.gradle:ktlint-gradle:9.2.1"
29 | classpath "com.vanniktech:gradle-maven-publish-plugin:0.25.3"
30 | }
31 | }
32 |
33 | plugins {
34 | id 'org.jetbrains.kotlinx.binary-compatibility-validator' version '0.13.2'
35 | }
36 |
37 | //apiValidation {
38 | // ignoredProjects += ["stately-collections", "stately-common", "stately-iso-collections", "stately-isolate"]
39 | //}
40 |
41 | allprojects {
42 | repositories {
43 | mavenLocal()
44 | mavenCentral()
45 | google()
46 | }
47 | }
48 |
49 | def runKtLint = false
50 |
51 | //subprojects {
52 | // if(runKtLint) {
53 | // apply plugin: "org.jlleitschuh.gradle.ktlint"
54 | // ktlint {
55 | // version.set("0.37.2")
56 | // enableExperimentalRules.set(true)
57 | // verbose.set(true)
58 | // filter {
59 | // exclude { it.file.path.contains("build/") }
60 | // }
61 | // }
62 | //
63 | // afterEvaluate {
64 | // tasks.named("check").configure {
65 | // dependsOn(tasks.getByName("ktlintCheck"))
66 | // }
67 | // }
68 | // }
69 | //}
--------------------------------------------------------------------------------
/convention-plugins/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 |
5 | dependencies {
6 | implementation(libs.kotlin.gradlePlugin)
7 | }
--------------------------------------------------------------------------------
/convention-plugins/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | }
5 | versionCatalogs {
6 | create("libs") {
7 | from(files("../gradle/libs.versions.toml"))
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/convention-plugins/src/main/kotlin/kmp-setup.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3 |
4 | plugins {
5 | kotlin("multiplatform")
6 | }
7 |
8 | kotlin {
9 | @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
10 | compilerOptions {
11 | freeCompilerArgs.add("-Xexpect-actual-classes")
12 | }
13 | jvm()
14 | js {
15 | nodejs {
16 | testTask {
17 | useMocha {
18 | // Override default timeout (needed for stress tests)
19 | timeout = "120s"
20 | }
21 | }
22 | }
23 | browser()
24 | }
25 | @OptIn(org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl::class)
26 | wasmJs {
27 | browser()
28 | nodejs()
29 | }
30 | macosX64()
31 | iosArm64()
32 | iosX64()
33 | watchosArm32()
34 | watchosArm64()
35 | watchosX64()
36 | watchosDeviceArm64()
37 | tvosArm64()
38 | tvosX64()
39 |
40 | macosArm64()
41 | iosSimulatorArm64()
42 | watchosSimulatorArm64()
43 | tvosSimulatorArm64()
44 |
45 | mingwX64()
46 | linuxX64()
47 | linuxArm64()
48 |
49 | androidNativeArm32()
50 | androidNativeArm64()
51 | androidNativeX86()
52 | androidNativeX64()
53 |
54 | @Suppress("OPT_IN_USAGE")
55 | applyDefaultHierarchyTemplate {
56 | common {
57 | group("jsAndWasmJs") {
58 | withJs()
59 | withWasm()
60 | }
61 | }
62 | }
63 | }
64 |
65 | rootProject.the().apply {
66 | nodeVersion = "21.0.0-v8-canary202309143a48826a08"
67 | nodeDownloadBaseUrl = "https://nodejs.org/download/v8-canary"
68 | }
69 |
70 | tasks.withType().all {
71 | kotlinOptions.jvmTarget = "1.8"
72 | }
--------------------------------------------------------------------------------
/deprecated/README.md:
--------------------------------------------------------------------------------
1 | # Stately - Deprecated
2 |
3 | The Kotlin/Native runtime previously had a unique ["strict" memory model](https://www.youtube.com/watch?v=oxQ6e1VeH4M), and Stately originally existed to facilitate developing common code with those constructs, as well as some other structures for living in that world. As of Kotlin 1.7.20, that strict memory model is deprecated, and as such, so are the Stately modules built to support it.
4 |
5 | They are sill available and will be published with Stately updates, but they are not being actively developed and, generally speaking, not actively supported (although critical fixes will probably happen).
6 |
7 | ## stately-common
8 |
9 | Kotlin/Native state adds some concepts that don't exist in either the JVM or JS, as well as the annotation @Throws.
10 |
11 | `stately-common` is very simple. It includes common definitions for `freeze()`, `isFrozen`, and `ensureNeverFrozen()`, and as mentioned `@Throws`. These definitions, 2 functions, a val, and an annotation, are often source-copied into other libraries. `stately-common`'s sole purpose is to define these as minimally as possible, to be included in apps or other libraries.
12 |
13 | On native, these values delegate or are typealiased to their platform definitions. In JS and the JVM, they do nothing.
14 |
15 | ### Config
16 |
17 | ```groovy
18 | commonMain {
19 | dependencies {
20 | implementation 'co.touchlab:stately-common:1.2.0'
21 | }
22 | }
23 | ```
24 |
25 | ## stately-isolate
26 |
27 | `stately-isolate` creates mutable state in a special state thread, and restricts access to that state from the same thread. This allows the state held by an instance of `IsolateState` to remain mutable. State coming in and going out must be frozen, but the guarded state can change.
28 |
29 | > Read more about the design in [this blog post](https://dev.to/touchlab/kotlin-native-isolated-state-50l1).
30 |
31 | The obvious use case is for collections. Example usage:
32 |
33 | ```kotlin
34 | fun usage(){
35 | val cacheMap = IsolateState { mutableMapOf() }
36 | val key = "abc"
37 | val value = "123"
38 | cacheMap.access { it.put(key, value) }
39 | val valueString = cacheMap.access { it.get(key) }
40 | println(valueString) // <- will print '123'
41 | }
42 | ```
43 |
44 | The `cacheMap` above can be called from multiple threads. The lambda passed to the `access` method will be invoked on the same thread that the state was created on. Because it is a single thread, access is serialized and thread-safe.
45 |
46 | You can create other instances of `IsolateState` by forking the parent instance.
47 |
48 | ```kotlin
49 | fun usage(){
50 | val cacheMap = IsolateState { mutableMapOf() }
51 | val key = "abc"
52 | val value = "123"
53 | cacheMap.access { it.put(key, value) }
54 |
55 | //Fork state
56 | val entrySet = cacheMap.access { map ->
57 | IsolateState(cacheMap.fork(map.entries))
58 | }
59 |
60 | val valueString = entrySet.access { entries -> entries.first().value }
61 | println(valueString) // <- will print '123'
62 | }
63 | ```
64 |
65 | You can create a class that extends `IsolateState` and provides for simpler access.
66 |
67 | ```kotlin
68 | class SimpleMap: IsolateState>({ mutableMapOf()})
69 | {
70 | fun put(key:K, value:V):V? = access { it.put(key, value) }
71 | fun get(key: K):V? = access { it.get(key) }
72 | }
73 | ```
74 |
75 | `stately-iso-collections` implements collections by extending `IsolateState` in this manner.
76 |
77 | You must dispose of `IsolateState` instances to avoid memory leaks.
78 |
79 | ```kotlin
80 | fun usage(){
81 | val cacheMap = IsolateState { mutableMapOf() }
82 | cacheMap.dispose()
83 | }
84 | ```
85 |
86 | ### Config
87 |
88 | ```kotlin
89 | commonMain {
90 | dependencies {
91 | implementation 'co.touchlab:stately-isolate:1.2.0'
92 | }
93 | }
94 | ```
95 |
96 | ## stately-iso-collections
97 |
98 | This is a set of mutable collections implemented with `IsolateState`. The set currently includes a `MutableSet`, `MutableList`,
99 | `MutableMap`, and an implementation of `ArrayDeque` that is being added to the Kotlin stdlib in 1.3.70.
100 |
101 |
102 |
103 | ### Config
104 |
105 | ```kotlin
106 | commonMain {
107 | dependencies {
108 | implementation 'co.touchlab:stately-iso-collections:1.2.0'
109 | }
110 | }
111 | ```
112 |
113 | ## stately-collections
114 |
115 | A set of collections that can be shared and accessed between threads. This is pretty much deprecated, but we have no plans to remove it as some production apps use it.
116 |
117 | However, ***we would strongly suggest you use `stately-isolate` and `stately-iso-collections` instead.*** Collections implemented with `stately-isolate` are far more flexible and absolutely CRUSH the original `stately-collections` in performance benchmarks. See [blog post](https://dev.to/touchlab/kotlin-native-isolated-state-50l1).
118 |
119 | ## Usage
120 |
121 | Dependencies can be specified in the common source set, as shown above, if you have Gradle metadata enabled in `settings.gradle`.
122 |
123 | ```groovy
124 | enableFeaturePreview('GRADLE_METADATA')
125 | ```
126 |
127 | ## We're Hiring!
128 |
129 | Touchlab is looking for a Mobile Developer, with Android/Kotlin experience, who is eager to dive into Kotlin Multiplatform Mobile (KMM) development. Come join the remote-first team putting KMM in production. [More info here](https://go.touchlab.co/careers-gh).
130 |
131 | ## Primary Maintainer
132 |
133 | [Kevin Galligan](https://github.com/kpgalligan/)
134 |
135 | 
136 |
137 | *Ping me on twitter [@kpgalligan](https://twitter.com/kpgalligan/) if you don't get a timely reply!* -Kevin
138 |
139 | License
140 | =======
141 |
142 | Copyright 2020 Touchlab, Inc.
143 |
144 | Licensed under the Apache License, Version 2.0 (the "License");
145 | you may not use this file except in compliance with the License.
146 | You may obtain a copy of the License at
147 |
148 | http://www.apache.org/licenses/LICENSE-2.0
149 |
150 | Unless required by applicable law or agreed to in writing, software
151 | distributed under the License is distributed on an "AS IS" BASIS,
152 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
153 | See the License for the specific language governing permissions and
154 | limitations under the License.
155 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | plugins {
18 | id 'org.jetbrains.kotlin.multiplatform'
19 | id 'kmp-setup'
20 | id 'com.vanniktech.maven.publish'
21 | }
22 |
23 | group = GROUP
24 | version = VERSION_NAME
25 |
26 | configurations {
27 | compileClasspath
28 | }
29 | kotlin {
30 | sourceSets {
31 | commonMain {
32 | dependencies {
33 | api project(":stately-common")
34 | api project(":stately-concurrency")
35 | }
36 | }
37 |
38 | commonTest {
39 | dependencies {
40 | implementation(kotlin("test"))
41 | implementation libs.testHelp
42 | }
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/commonMain/kotlin/co/touchlab/stately/collections/Functions.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.collections
18 |
19 | /**
20 | * Creates a copy-on-write list. On the JVM, will return
21 | * [https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CopyOnWriteArrayList.html](CopyOnWriteArrayList).
22 | * On Native, there is a native implementation which maintains and updates a frozen ArrayList.
23 | */
24 | expect fun frozenCopyOnWriteList(collection: Collection? = null): MutableList
25 |
26 | @Deprecated(
27 | message = "Replacing Atomic collections with isolated state collections (modele `stately-iso-collections`)",
28 | replaceWith = ReplaceWith(
29 | "sharedMutableListOf()",
30 | "co.touchlab.stately.collections.sharedMutableListOf"
31 | )
32 | )
33 | fun frozenLinkedList(stableIterator: Boolean = false): MutableList = if (stableIterator) {
34 | CopyOnIterateLinkedList()
35 | } else {
36 | SharedLinkedList()
37 | }
38 |
39 | @Deprecated(
40 | message = "Replacing Atomic collections with isolated state collections (modele `stately-iso-collections`)",
41 | replaceWith = ReplaceWith(
42 | "sharedMutableMapOf()",
43 | "co.touchlab.stately.collections.sharedMutableMapOf"
44 | )
45 | )
46 | fun frozenHashMap(initialCapacity: Int = 16, loadFactor: Float = 0.75.toFloat()): MutableMap =
47 | SharedHashMap(initialCapacity, loadFactor)
48 |
49 | @Deprecated(
50 | message = "Replacing Atomic collections with isolated state collections (modele `stately-iso-collections`)",
51 | replaceWith = ReplaceWith(
52 | "sharedMutableSetOf()",
53 | "co.touchlab.stately.collections.sharedMutableSetOf"
54 | )
55 | )
56 | fun frozenHashSet(): MutableSet = SharedSet()
57 |
58 | fun frozenLruCache(maxCacheSize: Int, onRemove: (MutableMap.MutableEntry) -> Unit = {}): LruCache =
59 | SharedLruCache(maxCacheSize, onRemove)
60 |
61 | /**
62 | * Creates a list from an Iterator.
63 | */
64 | fun Iterator.toList(): List {
65 | val result = mutableListOf()
66 | while (hasNext()) {
67 | result.add(next())
68 | }
69 | return result
70 | }
71 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/commonMain/kotlin/co/touchlab/stately/collections/ObjectPool.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import co.touchlab.stately.concurrency.AtomicInt
4 | import co.touchlab.stately.concurrency.AtomicReference
5 | import co.touchlab.stately.concurrency.Lock
6 | import co.touchlab.stately.concurrency.value
7 | import co.touchlab.stately.concurrency.withLock
8 | import co.touchlab.stately.freeze
9 |
10 | class ObjectPool(
11 | private val maxSize: Int,
12 | private val createBlock: () -> T,
13 | private val cleanupBlock: ((t: T) -> Unit)? = null
14 | ) {
15 | init {
16 | if (maxSize < 0) {
17 | throw IllegalArgumentException("maxSize cannot be negative")
18 | }
19 | }
20 |
21 | internal val pool = Array>(maxSize) {
22 | AtomicReference(null)
23 | }
24 |
25 | private val poolIndex = AtomicInt(0)
26 | private val lock = Lock()
27 |
28 | fun push(t: T): Boolean = lock.withLock {
29 | if (maxSize == 0) {
30 | cleanupBlock?.invoke(t)
31 | false
32 | } else {
33 | val index = poolIndex.value
34 |
35 | return if (index >= maxSize) {
36 | cleanupBlock?.invoke(t)
37 | false
38 | } else {
39 | pool[index].value = t
40 | poolIndex.incrementAndGet()
41 | true
42 | }
43 | }
44 | }
45 |
46 | fun pop(): T = lock.withLock {
47 | if (maxSize == 0) {
48 | createBlock().freeze()
49 | } else {
50 | val index = poolIndex.value
51 |
52 | val fromPool = if (index <= 0) {
53 | null
54 | } else {
55 | val ref = pool[poolIndex.decrementAndGet()]
56 | val t = ref.value
57 | ref.value = null
58 | t
59 | }
60 |
61 | fromPool ?: createBlock().freeze()
62 | }
63 | }
64 |
65 | fun clear() = lock.withLock {
66 | pool.forEach {
67 | val t = it.value
68 | if (t != null) {
69 | cleanupBlock?.invoke(t)
70 | it.value = null
71 | }
72 | }
73 |
74 | poolIndex.value = 0
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/commonMain/kotlin/co/touchlab/stately/collections/SharedLruCache.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.collections
18 |
19 | import co.touchlab.stately.concurrency.Lock
20 | import co.touchlab.stately.freeze
21 |
22 | /**
23 | * Implementation of a least recently used cache, multithreading aware for kotlin multiplatform.
24 | *
25 | * You provide a maximum number of cached entries, and (optionally) a lambda to call when something is removed.
26 | *
27 | * Operations on this collection aggressively lock, to err on the side of caution and sanity. Bear this in mind
28 | * if using in a high volume context.
29 | *
30 | * Each key will only retain a single value. When an entry is removed due to exceeding capacity, onRemove is called.
31 | * However, if adding an entry replaces an existing entry, the old entry will be returned, and onRemove WILL NOT be called.
32 | * If resource management depends on onRemove, keep this in mind. You'll need to handle a duplicate/replacement add.
33 | *
34 | * Along those lines, if you are managing resources with onRemove, keep in mind that if you abandon the cache and memory
35 | * is reclaimed, onRemove WILL NOT be called for existing entries. If you need to close resources, you'll have to implement
36 | * an explicit call to 'removeAll'. That will push all existing values to onRemove.
37 | *
38 | * IMPORTANT NOTE: Locking is not reentrant. As a result, onRemove is called AFTER the mutation lock is released, in
39 | * case the onRemove logic intends to call back into the cache. This may have unintended consequences if you're expecting
40 | * the mutation to be fully atomic in nature.
41 | */
42 | class SharedLruCache(
43 | private val maxCacheSize: Int,
44 | private val onRemove: (MutableMap.MutableEntry) -> Unit = {}
45 | ) : LruCache {
46 |
47 | private var lock: Lock = Lock()
48 | private val cacheMap = SharedHashMap>(initialCapacity = maxCacheSize)
49 | private val cacheList = SharedLinkedList(20)
50 |
51 | init {
52 | freeze()
53 | }
54 |
55 | /**
56 | * Stores value at key.
57 | *
58 | * If replacing an existing value, that value will be returned, but onRemove will not
59 | * be called.
60 | *
61 | * If adding a new value, if the total number of values exceeds maxCacheSize, the last accessed
62 | * value will be removed and sent to onRemove.
63 | *
64 | * If adding a value with the same key and value of an existing value, the LRU cache is updated, but
65 | * the existing value is not returned. This effectively refreshes the entry in the LRU list.
66 | */
67 | override fun put(key: K, value: V): V? {
68 | var resultValue: V? = null
69 | val removeCollection: MutableList> = ArrayList()
70 |
71 | withLock {
72 | val cacheEntry = cacheMap.get(key)
73 | val node: AbstractSharedLinkedList.Node
74 | val result: V?
75 | if (cacheEntry != null) {
76 | result = if (value != cacheEntry.v) {
77 | cacheEntry.v
78 | } else {
79 | null
80 | }
81 | node = cacheEntry.node
82 | node.readd()
83 | } else {
84 | result = null
85 | node = cacheList.addNode(key)
86 | }
87 | cacheMap.put(key, CacheEntry(value, node))
88 |
89 | while (cacheList.size > maxCacheSize) {
90 | val key = cacheList.removeAt(0)
91 | val entry = cacheMap.remove(key)
92 | if (entry != null) {
93 | removeCollection.add(LruEntry(key, entry.v))
94 | }
95 | }
96 |
97 | resultValue = result
98 | }
99 |
100 | removeCollection.forEach(onRemove)
101 |
102 | return resultValue
103 | }
104 |
105 | /**
106 | * Removes value at key (if it exists). If a value is found, it is passed to onRemove.
107 | */
108 | override fun remove(key: K, skipCallback: Boolean): V? {
109 | var removeEntry: LruEntry? = null
110 | withLock {
111 | val entry = cacheMap.remove(key)
112 | if (entry != null) {
113 | entry.node.remove()
114 | removeEntry = LruEntry(key, entry.v)
115 | }
116 | }
117 | if (!skipCallback && removeEntry != null) {
118 | onRemove(removeEntry!!)
119 | }
120 |
121 | return removeEntry?.value
122 | }
123 |
124 | /**
125 | * Returns all entries. This does not affect position in LRU cache. IE, old entries stay old.
126 | */
127 | override val entries: MutableSet>
128 | get() = withLock {
129 | return internalAll()
130 | }
131 |
132 | /**
133 | * Clears the cache. If skipCallback is set to true, onRemove is not called. Defaults to false.
134 | */
135 | override fun removeAll(skipCallback: Boolean) {
136 | var removeCollection: Collection>? = null
137 |
138 | withLock {
139 | if (!skipCallback) {
140 | removeCollection = internalAll()
141 | }
142 | cacheMap.clear()
143 | cacheList.clear()
144 | }
145 |
146 | if (removeCollection != null) {
147 | removeCollection!!.forEach(onRemove)
148 | }
149 | }
150 |
151 | /**
152 | * Finds and returns cache value, if it exists. If it exists, the key gets moved to the front of the
153 | * LRU list.
154 | */
155 | override fun get(key: K): V? = withLock {
156 | val cacheEntry = cacheMap.get(key)
157 | return if (cacheEntry != null) {
158 | cacheEntry!!.node.readd()
159 | cacheEntry.v
160 | } else {
161 | null
162 | }
163 | }
164 |
165 | /*
166 | This was OK with Intellij but kicking back an llvm error. To investigate.
167 | override fun get(key: K): V? = withLock {
168 | val cacheEntry = cacheMap.get(key)
169 | if(cacheEntry != null){
170 | cacheEntry.node.readd()
171 | return cacheEntry.v
172 | }
173 | else{
174 | return null
175 | }
176 | }
177 | */
178 |
179 | /**
180 | * Well...
181 | */
182 | override fun exists(key: K): Boolean = withLock { cacheMap.get(key) != null }
183 |
184 | override val size: Int
185 | get() = withLock { cacheMap.size }
186 |
187 | data class CacheEntry(val v: V, val node: AbstractSharedLinkedList.Node)
188 |
189 | class LruEntry(override val key: K, override val value: V) : MutableMap.MutableEntry {
190 |
191 | override fun setValue(newValue: V): V {
192 | throw UnsupportedOperationException()
193 | }
194 |
195 | override fun toString(): String {
196 | return "LruEntry(key=$key, value=$value)"
197 | }
198 | }
199 |
200 | private fun internalAll(): HashSet> {
201 | val set = HashSet>(cacheList.size)
202 | cacheList.iterator().forEach {
203 | set.add(LruEntry(it, cacheMap.get(it)!!.v))
204 | }
205 | return set
206 | }
207 |
208 | private inline fun withLock(proc: () -> T): T {
209 | lock.lock()
210 | try {
211 | return proc()
212 | } finally {
213 | lock.unlock()
214 | }
215 | }
216 |
217 | internal fun printDebug() {
218 | println("CACHELIST")
219 | cacheList.forEach {
220 | println(it)
221 | }
222 | println("CACHEMAP")
223 | cacheMap.entries.forEach {
224 | println(it)
225 | }
226 | }
227 | }
228 |
229 | interface LruCache {
230 | fun put(key: K, value: V): V?
231 | fun remove(key: K, skipCallback: Boolean = false): V?
232 | val entries: MutableSet>
233 | fun removeAll(skipCallback: Boolean = false)
234 | fun get(key: K): V?
235 | fun exists(key: K): Boolean
236 | val size: Int
237 | }
238 |
239 | typealias LruEntry = MutableMap.MutableEntry
240 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/commonMain/kotlin/co/touchlab/stately/collections/SharedSet.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.collections
18 |
19 | import co.touchlab.stately.freeze
20 |
21 | class SharedSet : MutableSet {
22 | private val backingMap = frozenHashMap()
23 |
24 | init {
25 | freeze()
26 | }
27 |
28 | override fun add(element: T): Boolean {
29 | val result = backingMap.containsKey(element)
30 | backingMap.put(element, Unit)
31 | return !result
32 | }
33 |
34 | override fun addAll(elements: Collection): Boolean =
35 | elements.fold(false) { b, t ->
36 | val result = backingMap.containsKey(t)
37 | backingMap.put(t, Unit)
38 | b || result
39 | }
40 |
41 | override fun clear() {
42 | backingMap.clear()
43 | }
44 |
45 | override fun remove(element: T): Boolean {
46 | val result = backingMap.containsKey(element)
47 | backingMap.remove(element)
48 | return result
49 | }
50 |
51 | override fun removeAll(elements: Collection): Boolean =
52 | elements.fold(false) { b, t ->
53 | val result = backingMap.containsKey(t)
54 | backingMap.remove(t)
55 | b || result
56 | }
57 |
58 | override fun retainAll(elements: Collection): Boolean {
59 | TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
60 | }
61 |
62 | override val size: Int
63 | get() = backingMap.size
64 |
65 | override fun contains(element: T): Boolean = backingMap.containsKey(element)
66 |
67 | override fun containsAll(elements: Collection): Boolean = backingMap.keys.containsAll(elements)
68 |
69 | override fun isEmpty(): Boolean = backingMap.isEmpty()
70 |
71 | override fun iterator(): MutableIterator = backingMap.keys.iterator()
72 | }
73 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/commonTest/kotlin/co/touchlab/stately/collections/JsIgnore.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | @OptIn(ExperimentalMultiplatform::class)
4 | @OptionalExpectation
5 | expect annotation class JsIgnore()
6 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ObjectPoolTest.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import co.touchlab.stately.concurrency.AtomicInt
4 | import co.touchlab.stately.concurrency.value
5 | import kotlin.test.Test
6 | import kotlin.test.assertEquals
7 | import kotlin.test.assertFails
8 | import kotlin.test.assertFalse
9 |
10 | class ObjectPoolTest {
11 | @Test
12 | fun pushMax() {
13 | var poolCount = 0
14 | val pool = ObjectPool(20, { PoolVal("arst ${poolCount++}") })
15 | val poolVals = Array(25) {
16 | pool.pop()
17 | }
18 |
19 | poolVals.forEach { pool.push(it) }
20 |
21 | assertEquals(20, pool.pool.size)
22 | assertFalse(pool.pool.any { it.value == null })
23 | }
24 |
25 | @Test
26 | fun cleanUpBlock() {
27 | var poolCount = 0
28 | val cleanCount = AtomicInt(0)
29 |
30 | val pool = ObjectPool(20, { PoolVal("arst ${poolCount++}") }) {
31 | cleanCount.incrementAndGet()
32 | }
33 |
34 | val poolVals = Array(25) {
35 | pool.pop()
36 | }
37 |
38 | poolVals.forEach { pool.push(it) }
39 |
40 | assertEquals(5, cleanCount.value)
41 | pool.clear()
42 | assertEquals(25, cleanCount.value)
43 | }
44 |
45 | @Test
46 | fun noNegativeSize() {
47 | assertFails { ObjectPool(maxSize = -2, createBlock = { PoolVal("arst") }) }
48 | }
49 |
50 | @Test
51 | fun noCacheWorks() {
52 | val dumpedList = ArrayList()
53 | val createdList = ArrayList()
54 | val pool = ObjectPool(0, { PoolVal("arst") }, { dumpedList.add(it) })
55 | for (i in 0 until 10) {
56 | createdList.add(pool.pop())
57 | }
58 |
59 | createdList.forEach {
60 | assertFalse(pool.push(it))
61 | }
62 |
63 | assertEquals(10, dumpedList.size)
64 | }
65 | }
66 |
67 | data class PoolVal(val s: String)
68 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/commonTest/kotlin/co/touchlab/stately/collections/SharedLruCacheTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.collections
18 |
19 | import co.touchlab.stately.concurrency.AtomicInt
20 | import co.touchlab.stately.concurrency.value
21 | import co.touchlab.testhelp.concurrency.ThreadOperations
22 | import co.touchlab.testhelp.isNative
23 | import co.touchlab.testhelp.isNativeFrozen
24 | import kotlin.test.Test
25 | import kotlin.test.assertEquals
26 | import kotlin.test.assertFails
27 | import kotlin.test.assertNull
28 | import kotlin.test.assertTrue
29 |
30 | class SharedLruCacheTest {
31 |
32 | @Test
33 | fun sanityCheck() {
34 | val collect = SharedLinkedList()
35 | val sc = SharedLruCache(4) {
36 | collect.add(it.value)
37 | }
38 |
39 | addEntries(6, sc)
40 |
41 | checkResults(collect, MapData("Value: 0"), MapData("Value: 1"))
42 |
43 | sc.get("Key: 2")
44 |
45 | sc.put("Asdf", MapData("Query"))
46 |
47 | // Key: 2 Should've been bumped to the front...
48 | assertEquals(collect.get(2), MapData("Value: 3"))
49 | }
50 |
51 | @Test
52 | fun put() {
53 | val collect = SharedLinkedList()
54 | val sc = SharedLruCache(4) {
55 | collect.add(it.value)
56 | }
57 |
58 | addEntries(4, sc)
59 |
60 | assertEquals(collect.size, 0)
61 |
62 | // One more than capacity
63 | addEntries(1, sc, 4)
64 |
65 | assertEquals(collect.size, 1)
66 | checkResults(collect, MapData("Value: 0"))
67 |
68 | // Adding duplicate value removes nothing, but bumps '1' to top of book
69 | val refreshResult = sc.put("Key: 1", MapData("Value: 1"))
70 | assertNull(refreshResult)
71 | assertEquals(collect.size, 1)
72 | checkResults(collect, MapData("Value: 0"))
73 |
74 | addEntries(1, sc, 5)
75 |
76 | checkResults(collect, MapData("Value: 0"), MapData("Value: 2"))
77 | }
78 |
79 | @Test
80 | fun remove() {
81 | val collect = SharedLinkedList()
82 | val sc = SharedLruCache(4) {
83 | collect.add(it.value)
84 | }
85 |
86 | addEntries(4, sc)
87 |
88 | sc.remove("Key: 2")
89 | sc.remove("Key: 0")
90 | sc.remove("Key: 5")
91 |
92 | checkResults(collect, MapData("Value: 2"), MapData("Value: 0"))
93 |
94 | assertEquals(sc.size, 2)
95 | sc.remove("Key: 1")
96 | sc.remove("Key: 3")
97 |
98 | checkResults(
99 | collect,
100 | MapData("Value: 2"),
101 | MapData("Value: 0"),
102 | MapData("Value: 1"),
103 | MapData("Value: 3")
104 | )
105 |
106 | assertEquals(sc.size, 0)
107 | }
108 |
109 | @Test
110 | fun removeSkip() {
111 | val collect = SharedLinkedList()
112 | val sc = SharedLruCache(4) {
113 | collect.add(it.value)
114 | }
115 |
116 | addEntries(4, sc)
117 |
118 | assertEquals(MapData("Value: 2"), sc.remove("Key: 2", skipCallback = true))
119 | assertEquals(MapData("Value: 0"), sc.remove("Key: 0", skipCallback = false))
120 | assertNull(sc.remove("Key: 5", skipCallback = true))
121 |
122 | assertEquals(1, collect.size)
123 | }
124 |
125 | @Test
126 | fun entries() {
127 | val collect = SharedLinkedList()
128 | val sc = SharedLruCache(4) {
129 | collect.add(it.value)
130 | }
131 |
132 | addEntries(4, sc)
133 |
134 | checkSetEntries(
135 | sc.entries,
136 | MapData("Value: 0"),
137 | MapData("Value: 1"),
138 | MapData("Value: 2"),
139 | MapData("Value: 3")
140 | )
141 |
142 | sc.remove("Key: 1")
143 | sc.remove("Key: 123")
144 |
145 | sc.get("Key: 2")
146 | sc.get("Key: 0")
147 | sc.put("a", MapData("1"))
148 |
149 | checkSetEntries(
150 | sc.entries,
151 | MapData("Value: 3"),
152 | MapData("Value: 2"),
153 | MapData("Value: 0"),
154 | MapData("1")
155 | )
156 |
157 | sc.put("b", MapData("2"))
158 |
159 | checkSetEntries(
160 | sc.entries,
161 | MapData("Value: 2"),
162 | MapData("Value: 0"),
163 | MapData("1"),
164 | MapData("2")
165 | )
166 | }
167 |
168 | @Test
169 | fun removeAll() {
170 | val collect = SharedLinkedList()
171 | val sc = SharedLruCache(4) {
172 | collect.add(it.value)
173 | }
174 |
175 | addEntries(4, sc)
176 |
177 | sc.removeAll()
178 |
179 | assertEquals(sc.size, 0)
180 | assertEquals(collect.size, 4)
181 |
182 | addEntries(4, sc)
183 |
184 | assertEquals(sc.size, 4)
185 | collect.clear()
186 | assertEquals(collect.size, 0)
187 |
188 | sc.removeAll(skipCallback = true)
189 | assertEquals(sc.size, 0)
190 | assertEquals(collect.size, 0)
191 | }
192 |
193 | @Test
194 | fun get() {
195 | val collect = SharedLinkedList()
196 | val sc = SharedLruCache(4) {
197 | collect.add(it.value)
198 | }
199 |
200 | addEntries(4, sc)
201 |
202 | assertEquals(collect.size, 0)
203 |
204 | sc.put("a", MapData("1"))
205 | assertEquals(collect.size, 1)
206 |
207 | assertEquals(sc.get("Key: 1"), MapData("Value: 1"))
208 | assertNull(sc.get("Key: 11"))
209 |
210 | sc.put("b", MapData("2"))
211 |
212 | checkResults(collect, MapData("Value: 0"), MapData("Value: 2"))
213 | }
214 |
215 | @Test
216 | fun exists() {
217 | val sc = SharedLruCache(4)
218 | addEntries(4, sc)
219 |
220 | checkExists(sc, "Key: 0", "Key: 1", "Key: 2", "Key: 3")
221 |
222 | sc.put("1", MapData("a"))
223 |
224 | checkExists(sc, "Key: 1", "Key: 2", "Key: 3", "1")
225 |
226 | sc.get("Key: 1")
227 | sc.put("2", MapData("b"))
228 |
229 | checkExists(sc, "Key: 1", "Key: 3", "1", "2")
230 |
231 | sc.remove("Key: 3")
232 |
233 | checkExists(sc, "Key: 1", "1", "2")
234 |
235 | sc.removeAll(skipCallback = true)
236 |
237 | checkExists(sc)
238 | }
239 |
240 | /*@Test
241 | fun initFrozen(){
242 | val sc = SharedLruCache(4)
243 | assertTrue(sc.isNativeFrozen())
244 | }*/
245 |
246 | /* @Test
247 | fun stress() {
248 | val MAX_CACHE_SIZE = 4
249 | val sc = SharedLruCache(MAX_CACHE_SIZE).freeze()
250 |
251 | sc.put("key 1", MapData("a"))
252 | sc.put("key 2", MapData("a"))
253 | sc.put("key 3", MapData("a"))
254 | sc.put("key 4", MapData("a"))
255 |
256 | val stopTime = currentTimeMillis() + (15 * 1000)
257 |
258 | val worker = MPWorker()
259 | worker.runBackground {
260 | var count = 5
261 | while (currentTimeMillis() < stopTime) {
262 | sc.put("key $count", MapData("val $count"))
263 | count++
264 | }
265 | }
266 |
267 | worker.runBackground {
268 | var count = 500_000
269 | while (currentTimeMillis() < stopTime) {
270 | sc.put("key $count", MapData("val $count"))
271 | count++
272 | }
273 | }
274 |
275 | while (currentTimeMillis() < stopTime) {
276 | if (sc.size != 4) {
277 | sc.printDebug()
278 | }
279 | assertEquals(4, sc.size)
280 | }
281 | }*/
282 |
283 | @Test
284 | fun mtPutStress() {
285 | val CACHE_SIZE = 100
286 | val LOOPS = 100
287 |
288 | val count = AtomicInt(0)
289 | val ops = ThreadOperations> {
290 | SharedLruCache(CACHE_SIZE) { count.incrementAndGet() }
291 | }
292 |
293 | for (i in 0 until LOOPS) {
294 | ops.exe {
295 | it.put("key $i", MapData("data $i"))
296 | }
297 | }
298 |
299 | val lru = ops.run(threads = 8, randomize = true)
300 | assertEquals(CACHE_SIZE, lru.size)
301 | assertEquals(LOOPS - CACHE_SIZE, count.value)
302 | }
303 |
304 | private fun checkExists(lru: LruCache, vararg keys: String) {
305 | keys.forEach { assertTrue { lru.exists(it) } }
306 | assertEquals(lru.size, keys.size)
307 | }
308 |
309 | private fun checkSetEntries(c: Set>, vararg elems: MapData) {
310 | val checkSet = HashSet()
311 | c.forEach { checkSet.add(it.value) }
312 | for (i in 0 until elems.size) {
313 | assertTrue(checkSet.contains(elems[i]))
314 | }
315 | assertEquals(c.size, elems.size)
316 | }
317 |
318 | private fun addEntries(count: Int, cache: SharedLruCache, startAt: Int = 0) {
319 | for (i in 0 until count) {
320 | val insertIndex = i + startAt
321 | cache.put("Key: $insertIndex", MapData("Value: $insertIndex"))
322 | }
323 | }
324 |
325 | private fun checkResults(c: List, vararg elems: Any) {
326 | for (i in 0 until c.size) {
327 | assertEquals(c.get(i), elems[i])
328 | }
329 |
330 | assertEquals(c.size, elems.size)
331 | }
332 | }
333 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/commonTest/kotlin/co/touchlab/stately/collections/SharedSetTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.collections
18 |
19 | import kotlin.test.Test
20 | import kotlin.test.assertEquals
21 |
22 | class SharedSetTest {
23 |
24 | @Test
25 | fun dataClassEquals() {
26 | val set = frozenHashSet()
27 | assertEquals(0, set.size)
28 | set.add(CollectionValues("asdf", 123))
29 | assertEquals(1, set.size)
30 |
31 | set.add(CollectionValues("asdf", 123))
32 | assertEquals(1, set.size)
33 |
34 | set.add(CollectionValues("asdf", 124))
35 | assertEquals(2, set.size)
36 | }
37 |
38 | data class CollectionValues(val a: String, val b: Int)
39 |
40 | /*@Test
41 | fun testLambdas(){
42 | val set = SharedSet<()->Unit>()
43 |
44 | val a: () -> Unit = { 1 + 2 }
45 | val b: () -> Unit = { 1 + 2 }
46 | val c: () -> Unit = { 2 + 2 }
47 |
48 | assertEquals(a, b)
49 | assertNotEquals(a, c)
50 |
51 | set.add(a)
52 | set.add(b)
53 | assertEquals(1, set.size)
54 |
55 | set.add(c)
56 | assertEquals(2, set.size)
57 | }*/
58 | }
59 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/jsAndWasmJsMain/kotlin/co/touchlab/stately/collections/Functions.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | /**
4 | * Creates a copy-on-write list. On the JVM, will return
5 | * [https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CopyOnWriteArrayList.html](CopyOnWriteArrayList).
6 | * On Native, there is a native implementation which maintains and updates a frozen ArrayList.
7 | */
8 | actual fun frozenCopyOnWriteList(collection: Collection?): MutableList {
9 | throw UnsupportedOperationException("Not for JS or Wasm")
10 | }
11 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/jsAndWasmJsTest/kotlin/co/touchlab/stately/collections/JsIgnore.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import kotlin.test.Ignore
4 |
5 | actual typealias JsIgnore = Ignore
6 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/jvmMain/kotlin/co/touchlab/stately/collections/FunctionsJVM.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.collections
18 |
19 | import java.util.concurrent.CopyOnWriteArrayList
20 |
21 | actual fun frozenCopyOnWriteList(collection: Collection?): MutableList {
22 | return if (collection == null) {
23 | CopyOnWriteArrayList()
24 | } else {
25 | CopyOnWriteArrayList(collection)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/nativeMain/kotlin/co/touchlab/stately/collections/CopyOnWriteList.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.collections
18 |
19 | import co.touchlab.stately.concurrency.Lock
20 | import kotlin.concurrent.AtomicInt
21 | import kotlin.concurrent.AtomicReference
22 | import kotlin.native.concurrent.freeze
23 |
24 | class CopyOnWriteList(elements: Collection) : MutableList {
25 |
26 | private val listData = AtomicReference>(ArrayList(elements).freeze())
27 | private val instanceLock = Lock()
28 |
29 | init {
30 | freeze()
31 | }
32 |
33 | constructor() : this(ArrayList(0))
34 | constructor(initialCapacity: Int = 0) : this(ArrayList(initialCapacity))
35 |
36 | private inline fun modifyList(proc: (MutableList) -> R): R {
37 | instanceLock.lock()
38 |
39 | try {
40 | val mutableList = ArrayList(listData.value)
41 | val result = proc(mutableList)
42 | listData.value = mutableList.freeze()
43 |
44 | return result
45 | } finally {
46 | instanceLock.unlock()
47 | }
48 | }
49 |
50 | /*private inline fun modifyListLockless(proc:(MutableList)->R):R{
51 | var updated = false
52 | var result:R? = null
53 | while (!updated){
54 | val orig = listData.value
55 | val mutableList = ArrayList(orig)
56 | result = proc(mutableList)
57 | updated = listData.compareAndSet(orig, mutableList.freeze())
58 | }
59 |
60 | return result!!
61 | }*/
62 |
63 | override val size: Int
64 | get() = listData.value.size
65 |
66 | override fun contains(element: T): Boolean = listData.value.contains(element)
67 |
68 | override fun containsAll(elements: Collection): Boolean = listData.value.containsAll(elements)
69 |
70 | override fun get(index: Int): T = listData.value.get(index)
71 |
72 | override fun indexOf(element: T): Int = listData.value.indexOf(element)
73 |
74 | override fun isEmpty(): Boolean = listData.value.isEmpty()
75 |
76 | override fun iterator(): MutableIterator =
77 | LocalIterator(listData.value)
78 |
79 | override fun lastIndexOf(element: T): Int = listData.value.lastIndexOf(element)
80 |
81 | override fun add(element: T): Boolean = modifyList { it.add(element) }
82 |
83 | override fun add(index: Int, element: T) = modifyList { it.add(index, element) }
84 |
85 | override fun addAll(index: Int, elements: Collection): Boolean = modifyList { it.addAll(index, elements) }
86 |
87 | override fun addAll(elements: Collection): Boolean = modifyList { it.addAll(elements) }
88 |
89 | override fun clear() = modifyList { it.clear() }
90 |
91 | override fun listIterator(): MutableListIterator =
92 | LocalListIterator(listData.value)
93 |
94 | override fun listIterator(index: Int): MutableListIterator =
95 | LocalListIterator(listData.value, index)
96 |
97 | override fun remove(element: T): Boolean = modifyList { it.remove(element) }
98 |
99 | override fun removeAll(elements: Collection): Boolean = modifyList { it.removeAll(elements) }
100 |
101 | override fun removeAt(index: Int): T = modifyList { it.removeAt(index) }
102 |
103 | override fun retainAll(elements: Collection): Boolean = modifyList { it.retainAll(elements) }
104 |
105 | override fun set(index: Int, element: T): T = modifyList { it.set(index, element) }
106 |
107 | override fun subList(fromIndex: Int, toIndex: Int): MutableList = modifyList { it.subList(fromIndex, toIndex) }
108 |
109 | private open class LocalIterator(private val list: List, startIndex: Int = 0) : MutableIterator {
110 | val index = AtomicInt(startIndex)
111 | override fun hasNext(): Boolean = list.size > index.value
112 |
113 | override fun next(): T = list.get(index.addAndGet(1) - 1)
114 |
115 | override fun remove() {
116 | throw UnsupportedOperationException("Can't mutate list from iterator")
117 | }
118 | }
119 |
120 | private class LocalListIterator(private val list: List, startIndex: Int = 0) :
121 | LocalIterator(list, startIndex), MutableListIterator {
122 | override fun hasPrevious(): Boolean = index.value > 0
123 |
124 | override fun nextIndex(): Int = index.value
125 |
126 | override fun previous(): T = list.get(index.addAndGet(-1))
127 |
128 | override fun previousIndex(): Int = index.value - 1
129 |
130 | override fun add(element: T) {
131 | throw UnsupportedOperationException()
132 | }
133 |
134 | override fun set(element: T) {
135 | throw UnsupportedOperationException()
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/deprecated/stately-collections/src/nativeMain/kotlin/co/touchlab/stately/collections/Functions.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.collections
18 |
19 | actual fun frozenCopyOnWriteList(collection: Collection?): MutableList {
20 | return if (collection == null) {
21 | CopyOnWriteList()
22 | } else {
23 | CopyOnWriteList(collection)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/deprecated/stately-common/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | plugins {
17 | id 'org.jetbrains.kotlin.multiplatform'
18 | id 'kmp-setup'
19 | id 'com.vanniktech.maven.publish'
20 | }
21 |
22 | group = GROUP
23 | version = VERSION_NAME
24 |
25 | kotlin {
26 | sourceSets {
27 | commonMain {}
28 |
29 | commonTest {
30 | dependencies {
31 | implementation(kotlin("test"))
32 | implementation libs.testHelp
33 | }
34 | }
35 | }
36 | }
37 |
38 | configurations {
39 | compileClasspath
40 | }
--------------------------------------------------------------------------------
/deprecated/stately-common/src/commonMain/kotlin/co/touchlab/stately/Helpers.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately
18 |
19 | /**
20 | * Method to freeze state. Calls the platform implementation of 'freeze' on native, and is a noop on other platforms.
21 | */
22 | expect fun T.freeze(): T
23 |
24 | /**
25 | * Determine if object is frozen. Will return false on non-native platforms.
26 | */
27 | expect val T.isFrozen: Boolean
28 |
29 | /**
30 | * Call on an object which should never be frozen. Will help debug when something inadvertently is.
31 | */
32 | expect fun Any.ensureNeverFrozen()
33 |
--------------------------------------------------------------------------------
/deprecated/stately-common/src/commonTest/kotlin/co/touchlab/stately/HelpersTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately
18 |
19 |
20 | class HelpersTest {
21 | // @Test
22 | // fun ensureNeverFrozenNoFreezeChild() {
23 | // if (!isNative) {
24 | // return
25 | // }
26 | //
27 | // val noFreeze = Hi("qwert")
28 | // noFreeze.ensureNeverFrozen()
29 | //
30 | // val nested = Nested(noFreeze)
31 | // assertFails { nested.freeze() }
32 | // }
33 | //
34 | // @Test
35 | // fun ensureNeverFrozenFailsTarget() {
36 | // if (!isNative) {
37 | // return
38 | // }
39 | //
40 | // val noFreeze = Hi("qwert")
41 | // noFreeze.ensureNeverFrozen()
42 | //
43 | // assertFalse(noFreeze.isFrozen)
44 | // assertFails { noFreeze.freeze() }
45 | // assertFalse(noFreeze.isFrozen)
46 | // }
47 | //
48 | // @Test
49 | // fun ensureNeverFrozenFailOnFrozen() {
50 | // if (!isNative) {
51 | // return
52 | // }
53 | //
54 | // val wasFrozen = Hi("qwert").freeze()
55 | // assertFails { wasFrozen.ensureNeverFrozen() }
56 | // }
57 | //
58 | // data class Hi(val s: String)
59 | // data class Nested(val hi: Hi)
60 | }
61 |
--------------------------------------------------------------------------------
/deprecated/stately-common/src/jsAndWasmJsMain/kotlin/co/touchlab/stately/HelpersJS.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately
18 |
19 | actual fun T.freeze(): T = this
20 |
21 | actual val T.isFrozen: Boolean
22 | get() = false
23 |
24 | actual fun Any.ensureNeverFrozen() {}
25 |
--------------------------------------------------------------------------------
/deprecated/stately-common/src/jvmMain/kotlin/co/touchlab/stately/HelpersJVM.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately
18 |
19 | actual fun T.freeze(): T = this
20 |
21 | actual val T.isFrozen: Boolean
22 | get() = false
23 |
24 | actual fun Any.ensureNeverFrozen() {}
25 |
--------------------------------------------------------------------------------
/deprecated/stately-common/src/nativeMain/kotlin/co/touchlab/stately/Helpers.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | @file:OptIn(FreezingIsDeprecated::class)
18 |
19 | package co.touchlab.stately
20 |
21 | import kotlin.native.concurrent.ensureNeverFrozen
22 | import kotlin.native.concurrent.freeze
23 | import kotlin.native.concurrent.isFrozen
24 |
25 | actual fun T.freeze(): T = this.freeze()
26 |
27 | actual val T.isFrozen: Boolean
28 | get() = this.isFrozen
29 |
30 | actual fun Any.ensureNeverFrozen() = this.ensureNeverFrozen()
31 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | plugins {
17 | id 'org.jetbrains.kotlin.multiplatform'
18 | id 'kmp-setup'
19 | id 'com.vanniktech.maven.publish'
20 | }
21 |
22 | group = GROUP
23 | version = VERSION_NAME
24 |
25 | kotlin {
26 | sourceSets {
27 | commonMain {
28 | dependencies {
29 | api project(":stately-isolate")
30 | }
31 | }
32 |
33 | commonTest {
34 | dependencies {
35 | implementation(kotlin("test"))
36 | implementation libs.testHelp
37 | }
38 | }
39 | }
40 | }
41 |
42 | configurations {
43 | compileClasspath
44 | }
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/karma.config.d/timeout-override.js:
--------------------------------------------------------------------------------
1 | // Override test timeout. Needed for stress tests
2 | config.set({
3 | "client": {
4 | "mocha": {
5 | "timeout": 120000
6 | },
7 | },
8 | "browserDisconnectTimeout": 120000
9 | });
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonMain/kotlin/co/touchlab/stately/collections/Functions.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | fun sharedMutableListOf(): IsoMutableList = IsoMutableList()
4 | fun sharedMutableListOf(vararg items: T): IsoMutableList = IsoMutableList { mutableListOf(*items) }
5 |
6 | fun sharedMutableSetOf(): IsoMutableSet = IsoMutableSet()
7 | fun sharedMutableSetOf(vararg items: T): IsoMutableSet = IsoMutableSet { mutableSetOf(*items) }
8 |
9 | fun sharedMutableMapOf(): IsoMutableMap = IsoMutableMap()
10 | fun sharedMutableMapOf(vararg items: Pair): IsoMutableMap = IsoMutableMap { mutableMapOf(*items) }
11 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonMain/kotlin/co/touchlab/stately/collections/IsoArrayDeque.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import co.touchlab.stately.isolate.StateHolder
4 | import co.touchlab.stately.isolate.StateRunner
5 | import co.touchlab.stately.isolate.createState
6 |
7 | open class IsoArrayDeque
8 | internal constructor(stateHolder: StateHolder>) :
9 | IsoMutableList(stateHolder) {
10 | constructor(stateRunner: StateRunner? = null, producer: () -> ArrayDeque = { ArrayDeque() }) : this(createState(stateRunner, producer))
11 |
12 | /**
13 | * Returns the first element, or throws [NoSuchElementException] if this deque is empty.
14 | */
15 | public fun first(): E = asAccess { it.first() }
16 |
17 | /**
18 | * Returns the first element, or `null` if this deque is empty.
19 | */
20 | public fun firstOrNull(): E? = asAccess { it.firstOrNull() }
21 |
22 | /**
23 | * Returns the last element, or throws [NoSuchElementException] if this deque is empty.
24 | */
25 | public fun last(): E = asAccess { it.last() }
26 |
27 | /**
28 | * Returns the last element, or `null` if this deque is empty.
29 | */
30 | public fun lastOrNull(): E? = asAccess { it.lastOrNull() }
31 |
32 | /**
33 | * Prepends the specified [element] to this deque.
34 | */
35 | public fun addFirst(element: E) = asAccess { it.addFirst(element) }
36 |
37 | /**
38 | * Appends the specified [element] to this deque.
39 | */
40 | public fun addLast(element: E) = asAccess { it.addLast(element) }
41 |
42 | /**
43 | * Removes the first element from this deque and returns that removed element, or throws [NoSuchElementException] if this deque is empty.
44 | */
45 | public fun removeFirst(): E = asAccess { it.removeFirst() }
46 |
47 | /**
48 | * Removes the first element from this deque and returns that removed element, or returns `null` if this deque is empty.
49 | */
50 | public fun removeFirstOrNull(): E? = asAccess { it.removeFirstOrNull() }
51 |
52 | /**
53 | * Removes the last element from this deque and returns that removed element, or throws [NoSuchElementException] if this deque is empty.
54 | */
55 | public fun removeLast(): E = asAccess { it.removeLast() }
56 |
57 | /**
58 | * Removes the last element from this deque and returns that removed element, or returns `null` if this deque is empty.
59 | */
60 | public fun removeLastOrNull(): E? = asAccess { it.removeLastOrNull() }
61 |
62 | private inline fun asAccess(crossinline block: (ArrayDeque) -> R): R =
63 | access { block(it as ArrayDeque) }
64 | }
65 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonMain/kotlin/co/touchlab/stately/collections/IsoMutableCollection.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import co.touchlab.stately.isolate.IsolateState
4 | import co.touchlab.stately.isolate.StateHolder
5 | import co.touchlab.stately.isolate.StateRunner
6 | import co.touchlab.stately.isolate.createState
7 |
8 | open class IsoMutableCollection internal constructor(stateHolder: StateHolder>) :
9 | IsolateState>(stateHolder), MutableCollection {
10 | constructor(stateRunner: StateRunner? = null, producer: () -> MutableCollection) : this(createState(stateRunner, producer))
11 |
12 | override fun equals(other: Any?): Boolean {
13 | return access { it == other }
14 | }
15 |
16 | override fun hashCode(): Int {
17 | return access { it.hashCode() }
18 | }
19 |
20 | override val size: Int
21 | get() = access { it.size }
22 |
23 | override fun contains(element: T): Boolean = access { it.contains(element) }
24 | override fun containsAll(elements: Collection): Boolean = access { it.containsAll(elements) }
25 | override fun isEmpty(): Boolean = access { it.isEmpty() }
26 | override fun add(element: T): Boolean = access { it.add(element) }
27 | override fun addAll(elements: Collection): Boolean = access { it.addAll(elements) }
28 | override fun clear() = access { it.clear() }
29 | override fun iterator(): MutableIterator = access { IsoMutableIterator(fork(it.iterator())) }
30 | override fun remove(element: T): Boolean = access { it.remove(element) }
31 | override fun removeAll(elements: Collection): Boolean = access { it.removeAll(elements) }
32 | override fun retainAll(elements: Collection): Boolean = access { it.retainAll(elements) }
33 | }
34 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonMain/kotlin/co/touchlab/stately/collections/IsoMutableIterator.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import co.touchlab.stately.isolate.IsolateState
4 | import co.touchlab.stately.isolate.StateHolder
5 |
6 | class IsoMutableIterator internal constructor(stateHolder: StateHolder>) :
7 | IsolateState>(stateHolder), MutableIterator {
8 | override fun hasNext(): Boolean = access { it.hasNext() }
9 | override fun next(): T = access { it.next() }
10 | override fun remove() = access { it.remove() }
11 | }
12 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonMain/kotlin/co/touchlab/stately/collections/IsoMutableList.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import co.touchlab.stately.isolate.IsolateState
4 | import co.touchlab.stately.isolate.StateHolder
5 | import co.touchlab.stately.isolate.StateRunner
6 | import co.touchlab.stately.isolate.createState
7 |
8 | open class IsoMutableList internal constructor(stateHolder: StateHolder>) :
9 | IsoMutableCollection(stateHolder), MutableList {
10 | constructor(stateRunner: StateRunner? = null, producer: () -> MutableList = { mutableListOf() }) : this(createState(stateRunner, producer))
11 |
12 | override fun get(index: Int): T = asAccess { it.get(index) }
13 | override fun indexOf(element: T): Int = asAccess { it.indexOf(element) }
14 | override fun lastIndexOf(element: T): Int = asAccess { it.lastIndexOf(element) }
15 | override fun add(index: Int, element: T) = asAccess { it.add(index, element) }
16 | override fun addAll(index: Int, elements: Collection): Boolean =
17 | asAccess { it.addAll(index, elements) }
18 | override fun listIterator(): MutableListIterator =
19 | asAccess { IsoMutableListIterator(fork(it.listIterator())) }
20 | override fun listIterator(index: Int): MutableListIterator =
21 | asAccess { IsoMutableListIterator(fork(it.listIterator(index))) }
22 | override fun removeAt(index: Int): T = asAccess { it.removeAt(index) }
23 | override fun set(index: Int, element: T): T = asAccess { it.set(index, element) }
24 | override fun subList(fromIndex: Int, toIndex: Int): MutableList = asAccess {
25 | IsoMutableList(fork(it.subList(fromIndex, toIndex)))
26 | }
27 |
28 | private inline fun asAccess(crossinline block: (MutableList) -> R): R = access { block(it as MutableList) }
29 | }
30 |
31 | class IsoMutableListIterator internal constructor(stateHolder: StateHolder>) :
32 | IsolateState>(stateHolder), MutableListIterator {
33 | override fun hasPrevious(): Boolean = access { it.hasPrevious() }
34 | override fun nextIndex(): Int = access { it.nextIndex() }
35 | override fun previous(): T = access { it.previous() }
36 | override fun previousIndex(): Int = access { it.previousIndex() }
37 | override fun add(element: T) = access { it.add(element) }
38 | override fun hasNext(): Boolean = access { it.hasNext() }
39 | override fun next(): T = access { it.next() }
40 | override fun set(element: T) = access { it.set(element) }
41 | override fun remove() = access { it.remove() }
42 | }
43 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonMain/kotlin/co/touchlab/stately/collections/IsoMutableMap.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import co.touchlab.stately.isolate.IsolateState
4 | import co.touchlab.stately.isolate.StateRunner
5 | import co.touchlab.stately.isolate.createState
6 |
7 | open class IsoMutableMap(stateRunner: StateRunner? = null, producer: () -> MutableMap = { mutableMapOf() }) :
8 | IsolateState>(createState(stateRunner, producer)), MutableMap {
9 | override val size: Int
10 | get() = access { it.size }
11 |
12 | override fun containsKey(key: K): Boolean = access { it.containsKey(key) }
13 | override fun containsValue(value: V): Boolean = access { it.containsValue(value) }
14 | override fun get(key: K): V? = access { it.get(key) }
15 | override fun isEmpty(): Boolean = access { it.isEmpty() }
16 | override val entries: MutableSet>
17 | get() {
18 | throw UnsupportedOperationException("Can't leak mutable reference")
19 | // access { IsoMutableSet(fork(it.entries)) }
20 | }
21 | override val keys: MutableSet
22 | get() = access { IsoMutableSet(fork(it.keys)) }
23 | override val values: MutableCollection
24 | get() = access { IsoMutableCollection(fork(it.values)) }
25 |
26 | override fun clear() = access { it.clear() }
27 | override fun put(key: K, value: V): V? = access { it.put(key, value) }
28 | override fun putAll(from: Map) = access { it.putAll(from) }
29 | override fun remove(key: K): V? = access { it.remove(key) }
30 |
31 | override fun equals(other: Any?): Boolean {
32 | return access { it.equals(other) }
33 | }
34 |
35 | override fun hashCode(): Int {
36 | return access { it.hashCode() }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonMain/kotlin/co/touchlab/stately/collections/IsoMutableSet.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import co.touchlab.stately.isolate.StateHolder
4 | import co.touchlab.stately.isolate.StateRunner
5 | import co.touchlab.stately.isolate.createState
6 |
7 | open class IsoMutableSet internal constructor(stateHolder: StateHolder>) :
8 | IsoMutableCollection(stateHolder), MutableSet {
9 | constructor(stateRunner: StateRunner? = null, producer: () -> MutableSet = { mutableSetOf() }) : this(createState(stateRunner, producer))
10 | }
11 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ExtendingCollections.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import co.touchlab.testhelp.concurrency.ThreadOperations
4 | import co.touchlab.testhelp.concurrency.sleep
5 | import kotlin.test.Test
6 | import kotlin.test.assertEquals
7 | import kotlin.test.assertTrue
8 |
9 | class ExtendingCollections {
10 | @Test
11 | @JsIgnore
12 | fun atomicAdd() {
13 | fun IsoMutableMap.sizeAdd(sd: SomeData) = access {
14 | val s = size
15 | sleep(200)
16 | put("key $s", sd)
17 | }
18 |
19 | val map = IsoMutableMap()
20 | val ops = ThreadOperations {}
21 | repeat(20) { i ->
22 | ops.exe {
23 | map.sizeAdd(SomeData("val $i"))
24 | }
25 | ops.test {
26 | assertTrue(map.containsKey("key $i"))
27 | }
28 | }
29 |
30 | ops.run(4, true)
31 |
32 | assertEquals(20, map.size)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonTest/kotlin/co/touchlab/stately/collections/IsoArrayDequeTest.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | class IsoArrayDequeTest : IsoMutableListTest() {
4 | override fun defaultCollection(): IsoMutableCollection = IsoArrayDeque()
5 | }
6 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonTest/kotlin/co/touchlab/stately/collections/IsoMutableListTest.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import kotlin.test.Test
4 | import kotlin.test.assertEquals
5 | import kotlin.test.assertTrue
6 |
7 | open class IsoMutableListTest : IsoMutableCollectionTest() {
8 | override fun defaultCollection(): IsoMutableCollection = IsoMutableList()
9 |
10 | @Test
11 | fun get() {
12 | val list = addABunch(threads = 1) as IsoMutableList
13 | assertEquals(list.get(234), SomeData("key 234"))
14 | assertEquals(list.get(434), SomeData("key 434"))
15 | }
16 |
17 | @Test
18 | fun indexOf() {
19 | val list = addABunch(threads = 1) as IsoMutableList
20 | assertEquals(list.indexOf(SomeData("key 234")), 234)
21 | }
22 |
23 | @Test
24 | fun lastIndexOf() {
25 | val list = addABunch(threads = 1) as IsoMutableList
26 | addABunch(list, threads = 1)
27 | assertEquals(list.lastIndexOf(SomeData("key 234")), 734)
28 | }
29 |
30 | @Test
31 | fun removeAt() {
32 | val list = addABunch(threads = 1) as IsoMutableList
33 | assertEquals(list.removeAt(234), SomeData("key 234"))
34 | assertEquals(list.size, 499)
35 | }
36 |
37 | @Test
38 | fun set() {
39 | val list = addABunch(threads = 1) as IsoMutableList
40 | assertEquals(list.set(234, SomeData("key 2340")), SomeData("key 234"))
41 | assertEquals(list.size, 500)
42 | }
43 |
44 | @Test
45 | fun subList() {
46 | val list = addABunch(threads = 1) as IsoMutableList
47 | assertEquals(
48 | list.subList(234, 237),
49 | listOf(
50 | SomeData("key 234"),
51 | SomeData("key 235"),
52 | SomeData("key 236")
53 | )
54 | )
55 | }
56 |
57 | @Test
58 | fun listIterator() {
59 | val list = addABunch() as IsoMutableList
60 | val reconstruct = mutableListOf()
61 | list.listIterator().forEach { reconstruct.add(it) }
62 | assertEquals(reconstruct, list)
63 | }
64 |
65 | @Test
66 | fun listIteratorIndex() {
67 | val list = addABunch() as IsoMutableList
68 | val reconstruct = mutableListOf()
69 | list.listIterator(122).forEach { reconstruct.add(it) }
70 | assertEquals(reconstruct, list.subList(122, list.size))
71 | }
72 |
73 | @Test
74 | fun addAll() {
75 | val set = addABunch()
76 | assertEquals(set.size, 500)
77 | set.addAll(listOf(SomeData("a"), SomeData("b"), SomeData("a")))
78 | assertEquals(set.size, 503)
79 | }
80 |
81 | @Test
82 | fun equalsTest() {
83 | fun makeSome(): IsoMutableList {
84 | val l = IsoMutableList()
85 | repeat(20) {
86 | l.add(SomeData("key $it"))
87 | }
88 | return l
89 | }
90 |
91 | val l1 = makeSome()
92 | val l2 = makeSome()
93 | assertTrue(l1.equals(l2))
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonTest/kotlin/co/touchlab/stately/collections/IsoMutableMapTest.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import co.touchlab.testhelp.concurrency.ThreadOperations
4 | import co.touchlab.testhelp.isNative
5 | import kotlin.random.Random
6 | import kotlin.test.Test
7 | import kotlin.test.assertEquals
8 | import kotlin.test.assertFalse
9 | import kotlin.test.assertNotNull
10 | import kotlin.test.assertNull
11 | import kotlin.test.assertTrue
12 |
13 | class IsoMutableMapTest {
14 | // @Test
15 | fun basicTest() {
16 | val map = IsoMutableMap()
17 | val ops = ThreadOperations {}
18 | repeat(100) { outer ->
19 | ops.exe {
20 | val lmap = mutableMapOf()
21 | val base = outer * 1_000
22 | repeat(1_000) {
23 | val num = it + base
24 | lmap.put("key $num", SomeData("val $num"))
25 | }
26 |
27 | map.putAll(lmap)
28 | }
29 | }
30 |
31 | ops.run(4)
32 |
33 | println("Added, total ${map.size}")
34 | }
35 |
36 | @Test
37 | fun containsKey() {
38 | val map = testMap()
39 | repeat(50) { i ->
40 | val r = Random.nextInt(500)
41 | assertTrue(map.containsKey("key $r"))
42 | assertFalse(map.containsKey("keya $r"))
43 | }
44 | }
45 |
46 | @Test
47 | fun containsValue() {
48 | val map = testMap()
49 | repeat(50) { i ->
50 | val r = Random.nextInt(500)
51 | assertTrue(map.containsValue(SomeData("val $r")))
52 | assertFalse(map.containsValue(SomeData("vala $r")))
53 | }
54 | }
55 |
56 | @Test
57 | fun get() {
58 | val map = testMap()
59 | repeat(50) { i ->
60 | val r = Random.nextInt(500)
61 | assertNotNull(map.get("key $r"))
62 | assertNull(map.get("keya $r"))
63 | }
64 | }
65 |
66 | @Test
67 | fun isEmpty() {
68 | val map = IsoMutableMap()
69 | assertTrue(map.isEmpty())
70 | testMap(map)
71 | assertFalse(map.isEmpty())
72 | }
73 |
74 | // @Test
75 | fun entries() {
76 | if (!isNative) {
77 | return
78 | }
79 |
80 | val map = testMap()
81 | val entries = map.entries
82 | assertEquals(500, map.size)
83 | println("b 1")
84 | val first = entries.first()
85 | println("b 2")
86 | entries.remove(first)
87 | println("b 3")
88 | assertEquals(499, map.size)
89 | }
90 |
91 | @Test
92 | fun keys() {
93 | val map = testMap()
94 | val keys = map.keys
95 | assertEquals(500, map.size)
96 | val r = mutableListOf()
97 | keys.forEachIndexed { index, s ->
98 | if (index % 10 == 0) {
99 | r.add(s)
100 | }
101 | }
102 |
103 | r.forEach { keys.remove(it) }
104 | assertEquals(450, map.size)
105 | }
106 |
107 | @Test
108 | fun values() {
109 | val map = testMap()
110 | val values = map.values
111 | values.remove(values.first())
112 | assertEquals(499, map.size)
113 | }
114 |
115 | @Test
116 | fun clear() {
117 | val map = testMap()
118 | map.clear()
119 | assertEquals(0, map.size)
120 | }
121 |
122 | @Test
123 | fun put() {
124 | val map = testMap()
125 | assertEquals(500, map.size)
126 | }
127 |
128 | @Test
129 | fun putAll() {
130 | val map = testMap()
131 | assertEquals(500, map.size)
132 |
133 | map.putAll(
134 | mapOf(
135 | Pair("a", SomeData("a")),
136 | Pair("b", SomeData("b")),
137 | Pair("c", SomeData("c")),
138 | Pair("a", SomeData("a"))
139 | )
140 | )
141 |
142 | assertEquals(503, map.size)
143 | }
144 |
145 | @Test
146 | fun remove() {
147 | val map = testMap()
148 | assertEquals(500, map.size)
149 | map.remove("key 432")
150 | map.remove("key 232")
151 | map.remove("key2 232")
152 | assertEquals(498, map.size)
153 | }
154 |
155 | @Test
156 | fun equalsTest() {
157 | val isomap = testMap()
158 | val map = mutableMapOf()
159 | repeat(500) { i ->
160 | map.put("key $i", SomeData("val $i"))
161 | }
162 |
163 | assertTrue(isomap.equals(map))
164 | }
165 |
166 | private fun testMap(map: IsoMutableMap = IsoMutableMap()): IsoMutableMap {
167 | repeat(500) { i ->
168 | map.put("key $i", SomeData("val $i"))
169 | }
170 |
171 | return map
172 | }
173 | }
174 |
175 | data class SomeData(val s: String)
176 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonTest/kotlin/co/touchlab/stately/collections/IsoMutableSetTest.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import co.touchlab.testhelp.concurrency.ThreadOperations
4 | import kotlin.random.Random
5 | import kotlin.test.Test
6 | import kotlin.test.assertEquals
7 | import kotlin.test.assertFalse
8 | import kotlin.test.assertTrue
9 |
10 | class IsoMutableSetTest : IsoMutableCollectionTest() {
11 | override fun defaultCollection(): IsoMutableCollection = IsoMutableSet()
12 |
13 | @Test
14 | fun addAll() {
15 | val set = addABunch()
16 | assertEquals(set.size, 500)
17 | set.addAll(listOf(SomeData("a"), SomeData("b"), SomeData("a")))
18 | assertEquals(set.size, 502)
19 | }
20 |
21 | @Test
22 | fun equalsTest() {
23 | val set = addABunch()
24 | val set2 = addABunch()
25 | assertTrue(set.equals(set2))
26 | }
27 | }
28 |
29 | abstract class IsoMutableCollectionTest {
30 |
31 | @Test
32 | fun stress() {
33 | val set = defaultCollection()
34 |
35 | val ops = ThreadOperations {}
36 | val times = 30_000
37 | repeat(times) { i ->
38 | ops.exe { set.add(SomeData("key $i")) }
39 | ops.test { assertTrue(set.contains(SomeData("key $i"))) }
40 | }
41 |
42 | ops.run(4)
43 |
44 | assertEquals(set.size, times)
45 | }
46 |
47 | @Test
48 | fun contains() {
49 | val set = addABunch()
50 | repeat(500) { i ->
51 | assertTrue(set.contains(SomeData("key $i")))
52 | }
53 | }
54 |
55 | @Test
56 | fun containsAll() {
57 | val set = addABunch()
58 |
59 | val checkList = mutableListOf()
60 | repeat(500) { i ->
61 | if (Random.nextDouble() > .8) {
62 | checkList.add(SomeData("key $i"))
63 | }
64 | }
65 |
66 | // In theory, this will fail occasionally, but pretty rare
67 | assertTrue(checkList.size > 0)
68 |
69 | assertTrue(set.containsAll(checkList))
70 | }
71 |
72 | @Test
73 | fun isEmpty() {
74 | val set = defaultCollection()
75 | assertTrue(set.isEmpty())
76 | addABunch(set)
77 | assertFalse(set.isEmpty())
78 | }
79 |
80 | @Test
81 | fun add() {
82 | contains() // Contains really covers this
83 | }
84 |
85 | @Test
86 | fun clear() {
87 | val set = addABunch()
88 | assertEquals(set.size, 500)
89 | set.clear()
90 | assertEquals(set.size, 0)
91 | }
92 |
93 | @Test
94 | fun iterator() {
95 | val set = addABunch()
96 | val iterator = set.iterator()
97 | var count = 0
98 | iterator.forEach { count++ }
99 | assertEquals(500, count)
100 | }
101 |
102 | @Test
103 | fun remove() {
104 | val set = addABunch()
105 |
106 | assertTrue(set.remove(SomeData("key 55")))
107 | assertTrue(set.remove(SomeData("key 155")))
108 | assertFalse(set.remove(SomeData("key 155")))
109 | assertFalse(set.remove(SomeData("key2 255")))
110 |
111 | assertEquals(498, set.size)
112 | }
113 |
114 | @Test
115 | fun removeAll() {
116 | val set = addABunch()
117 |
118 | assertTrue(
119 | set.removeAll(
120 | listOf(
121 | SomeData("key 55"),
122 | SomeData("key 155"),
123 | SomeData("key2 65")
124 | )
125 | )
126 | )
127 |
128 | assertEquals(498, set.size)
129 |
130 | assertTrue(
131 | set.removeAll(
132 | listOf(
133 | SomeData("key 65"),
134 | SomeData("key 165")
135 | )
136 | )
137 | )
138 |
139 | assertEquals(496, set.size)
140 |
141 | assertFalse(
142 | set.removeAll(
143 | listOf(
144 | SomeData("key 65"),
145 | SomeData("key 165")
146 | )
147 | )
148 | )
149 |
150 | assertEquals(496, set.size)
151 | }
152 |
153 | @Test
154 | fun retainAll() {
155 | val set = addABunch()
156 |
157 | assertTrue(
158 | set.retainAll(
159 | listOf(
160 | SomeData("key 55"),
161 | SomeData("key 155"),
162 | SomeData("key2 65")
163 | )
164 | )
165 | )
166 |
167 | assertEquals(2, set.size)
168 |
169 | assertFalse(
170 | set.retainAll(
171 | listOf(
172 | SomeData("key 55"),
173 | SomeData("key 155"),
174 | SomeData("key2 65")
175 | )
176 | )
177 | )
178 |
179 | assertEquals(2, set.size)
180 | }
181 |
182 | abstract fun defaultCollection(): IsoMutableCollection
183 |
184 | internal fun addABunch(set: IsoMutableCollection = defaultCollection(), threads: Int = 4): IsoMutableCollection {
185 | val ops = ThreadOperations {}
186 | repeat(500) { i ->
187 | ops.exe { set.add(SomeData("key $i")) }
188 | }
189 |
190 | ops.run(threads)
191 |
192 | return set
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/commonTest/kotlin/co/touchlab/stately/collections/JsIgnore.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | @OptIn(ExperimentalMultiplatform::class)
4 | @OptionalExpectation
5 | expect annotation class JsIgnore()
6 |
--------------------------------------------------------------------------------
/deprecated/stately-iso-collections/src/jsAndWasmJsTest/kotlin/co/touchlab/stately/collections/JsIgnore.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.collections
2 |
3 | import kotlin.test.Ignore
4 |
5 | actual typealias JsIgnore = Ignore
6 |
--------------------------------------------------------------------------------
/deprecated/stately-isolate/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | plugins {
17 | id 'org.jetbrains.kotlin.multiplatform'
18 | id 'kmp-setup'
19 | id 'com.vanniktech.maven.publish'
20 | }
21 |
22 | group = GROUP
23 | version = VERSION_NAME
24 |
25 | kotlin {
26 | sourceSets {
27 | commonMain {
28 | dependencies {
29 | api project(":stately-common")
30 | api project(":stately-concurrency")
31 | }
32 | }
33 |
34 | commonTest {
35 | dependencies {
36 | implementation(kotlin("test"))
37 | implementation libs.testHelp
38 | }
39 | }
40 | }
41 | }
42 |
43 | configurations {
44 | compileClasspath
45 | }
--------------------------------------------------------------------------------
/deprecated/stately-isolate/src/commonMain/kotlin/co/touchlab/stately/isolate/IsoState.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.isolate
2 |
3 | import kotlin.native.concurrent.SharedImmutable
4 |
5 | expect class StateHolder internal constructor(t: T, stateRunner: StateRunner) {
6 | val isDisposed: Boolean
7 | val myThread: Boolean
8 | val stateRunner: StateRunner
9 | val myState: T
10 | fun dispose()
11 | }
12 |
13 | open class IsolateState constructor(private val stateHolder: StateHolder) {
14 | constructor(stateRunner: StateRunner? = null, producer: () -> T) : this(createState(stateRunner, producer))
15 |
16 | val isDisposed: Boolean
17 | get() = stateHolder.isDisposed
18 |
19 | fun fork(r: R): StateHolder = if (stateHolder.myThread) {
20 | StateHolder(r, stateHolder.stateRunner)
21 | } else {
22 | throw IllegalStateException("Must fork state from the state thread")
23 | }
24 |
25 | fun access(block: (T) -> R): R {
26 | return if (stateHolder.myThread) {
27 | block(stateHolder.myState)
28 | } else {
29 | stateHolder.stateRunner.stateRun {
30 | block(stateHolder.myState)
31 | }
32 | }
33 | }
34 |
35 | fun dispose() = if (stateHolder.myThread) {
36 | stateHolder.dispose()
37 | } else {
38 | stateHolder.stateRunner.stateRun {
39 | stateHolder.dispose()
40 | }
41 | }
42 | }
43 |
44 | @SharedImmutable
45 | internal val defaultStateRunner: BackgroundStateRunner = BackgroundStateRunner()
46 |
47 | fun createState(stateRunner: StateRunner?, producer: () -> T): StateHolder {
48 | val runner = stateRunner ?: defaultStateRunner
49 | return runner.stateRun { StateHolder(producer(), runner) }
50 | }
51 |
52 | /**
53 | * Hook to shutdown iso-state default runtime
54 | */
55 | fun shutdownIsoRunner() {
56 | defaultStateRunner.stop()
57 | }
58 |
59 | internal sealed class RunResult
60 | internal data class Ok(val result: T) : RunResult()
61 | internal data class Thrown(val throwable: Throwable) : RunResult()
62 |
--------------------------------------------------------------------------------
/deprecated/stately-isolate/src/commonMain/kotlin/co/touchlab/stately/isolate/StateRunner.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.isolate
2 |
3 | interface StateRunner {
4 | fun stateRun(block: () -> R): R
5 | fun stop()
6 | }
7 |
8 | expect class BackgroundStateRunner constructor() : StateRunner {
9 | override fun stateRun(block: () -> R): R
10 | override fun stop()
11 | }
12 |
--------------------------------------------------------------------------------
/deprecated/stately-isolate/src/commonTest/kotlin/co/touchlab/stately/isolate/IsoStateTest.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.isolate
2 |
3 | import co.touchlab.stately.concurrency.ThreadRef
4 | import co.touchlab.stately.freeze
5 | import co.touchlab.testhelp.concurrency.ThreadOperations
6 | import co.touchlab.testhelp.concurrency.background
7 | import co.touchlab.testhelp.isNative
8 | import kotlin.test.Test
9 | import kotlin.test.assertEquals
10 | import kotlin.test.assertFails
11 | import kotlin.test.assertTrue
12 | import kotlin.test.fail
13 |
14 | class IsoStateTest {
15 | @Test
16 | fun basicTest() {
17 | val ops = ThreadOperations {}
18 |
19 | val isoList = IsolateState { mutableListOf() }
20 | repeat(5_000) { rcount ->
21 | ops.exe {
22 | isoList.access { l ->
23 | l.add(SomeData("arst $rcount"))
24 | }
25 | }
26 | }
27 |
28 | ops.run(4)
29 |
30 | val lsize = isoList.access { l ->
31 | l.size
32 | }
33 |
34 | assertEquals(5_000, lsize)
35 | }
36 |
37 |
38 |
39 | @Test
40 | fun throwExceptions() {
41 | val iso = IsolateState { mutableListOf("a") }
42 | try {
43 | iso.access { throw IllegalStateException("arst") }
44 | fail("Shouldn't be here")
45 | } catch (e: Exception) {
46 | println("Exception: $e")
47 | e.printStackTrace()
48 | assertTrue(e is IllegalStateException && e.message == "arst")
49 | }
50 | }
51 |
52 | class LeakyState : IsolateState>(producer = { mutableListOf() }) {
53 | fun leak() {
54 | var l: MutableList? = null
55 | access { l = it }
56 | l
57 | }
58 | }
59 |
60 | data class SomeData(val s: String)
61 |
62 | @Test
63 | fun testNonMainThread() {
64 | val bar = background {
65 | val foo = IsolateState { mutableListOf() }
66 | val s = "arst"
67 | foo.access {
68 | it.add(s)
69 | }
70 | foo
71 | }
72 |
73 | assertEquals(bar.access { it.get(0) }, "arst")
74 | }
75 |
76 | @Test
77 | fun stateRunsOnSameThreadByDefault() {
78 | val first = IsolateState { mutableListOf() }
79 | val second = IsolateState { mutableListOf() }
80 |
81 | val firstThread = first.access { ThreadRef() }
82 | val isSame = second.access { firstThread.same() }
83 |
84 | assertTrue { isSame }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/deprecated/stately-isolate/src/jsAndWasmJsMain/kotlin/co/touchlab/stately/isolate/BackgroundStateRunner.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.isolate
2 |
3 | actual class BackgroundStateRunner actual constructor() : StateRunner {
4 | actual override fun stateRun(block: () -> R): R = block()
5 | actual override fun stop() {
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/deprecated/stately-isolate/src/jsAndWasmJsMain/kotlin/co/touchlab/stately/isolate/StateHolder.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.isolate
2 |
3 | actual class StateHolder actual constructor(t: T, actual val stateRunner: StateRunner) {
4 | actual val myState: T = t
5 |
6 | private var _isDisposed: Boolean = false
7 |
8 | actual val isDisposed: Boolean
9 | get() = _isDisposed
10 |
11 | actual fun dispose() {
12 | if (!isDisposed) _isDisposed = true
13 | }
14 |
15 | actual val myThread: Boolean = true
16 | }
17 |
--------------------------------------------------------------------------------
/deprecated/stately-isolate/src/jvmMain/kotlin/co/touchlab/stately/isolate/BackgroundStateRunner.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.isolate
2 |
3 | import java.util.concurrent.Callable
4 | import java.util.concurrent.Executors
5 |
6 | actual class BackgroundStateRunner actual constructor() : StateRunner {
7 | internal val stateExecutor = Executors.newSingleThreadExecutor()
8 |
9 | actual override fun stateRun(block: () -> R): R {
10 | val result = stateExecutor.submit(
11 | Callable {
12 | try {
13 | Ok(block())
14 | } catch (e: Throwable) {
15 | Thrown(e)
16 | }
17 | }
18 | ).get()
19 |
20 | return when (result) {
21 | is Ok<*> -> result.result as R
22 | is Thrown -> throw result.throwable
23 | }
24 | }
25 |
26 | actual override fun stop() {
27 | stateExecutor.shutdown()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/deprecated/stately-isolate/src/jvmMain/kotlin/co/touchlab/stately/isolate/StateHolder.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.isolate
2 |
3 | import co.touchlab.stately.concurrency.ThreadRef
4 | import java.util.concurrent.atomic.AtomicBoolean
5 |
6 | actual class StateHolder actual constructor(t: T, actual val stateRunner: StateRunner) {
7 | actual val myState: T = t
8 |
9 | private var _isDisposed: AtomicBoolean = AtomicBoolean(false)
10 |
11 | actual val isDisposed: Boolean
12 | get() = _isDisposed.get()
13 |
14 | actual fun dispose() {
15 | _isDisposed.set(true)
16 | }
17 |
18 | private val threadRef = ThreadRef()
19 | actual val myThread: Boolean
20 | get() = threadRef.same()
21 | }
22 |
--------------------------------------------------------------------------------
/deprecated/stately-isolate/src/nativeMain/kotlin/co/touchlab/stately/isolate/BackgroundStateRunner.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.isolate
2 |
3 | import kotlin.native.concurrent.TransferMode
4 | import kotlin.native.concurrent.Worker
5 | import kotlin.native.concurrent.freeze
6 |
7 | actual class BackgroundStateRunner actual constructor() : StateRunner {
8 | internal val stateWorker = Worker.start(errorReporting = false)
9 |
10 | actual override fun stateRun(block: () -> R): R {
11 | val result = stateWorker.execute(
12 | TransferMode.SAFE, { block.freeze() },
13 | {
14 | try {
15 | Ok(it()).freeze()
16 | } catch (e: Throwable) {
17 | Thrown(e).freeze()
18 | }
19 | }
20 | ).result
21 | return when (result) {
22 | is Ok<*> -> result.result as R
23 | is Thrown -> throw result.throwable
24 | }
25 | }
26 |
27 | actual override fun stop() {
28 | stateWorker.requestTermination()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/deprecated/stately-isolate/src/nativeMain/kotlin/co/touchlab/stately/isolate/Platform.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.isolate
2 |
3 | import co.touchlab.stately.concurrency.GuardedStableRef
4 | import co.touchlab.stately.concurrency.ThreadRef
5 | import kotlin.native.concurrent.ensureNeverFrozen
6 | import kotlin.native.concurrent.isFrozen
7 |
8 | /**
9 | * Do not directly use this. You will have state issues. You can only interact with this class
10 | * from the state thread.
11 | */
12 | actual class StateHolder actual constructor(t: T, actual val stateRunner: StateRunner) {
13 | private val stableRef: GuardedStableRef
14 |
15 | init {
16 | if (t.isFrozen) {
17 | throw IllegalStateException("Mutable state shouldn't be frozen")
18 | }
19 | t.ensureNeverFrozen()
20 | stableRef = GuardedStableRef(t)
21 | }
22 |
23 | actual val myState: T
24 | get() = stableRef.state
25 |
26 | actual fun dispose() {
27 | stableRef.dispose()
28 | }
29 |
30 | private val threadRef = ThreadRef()
31 | actual val myThread: Boolean
32 | get() = threadRef.same()
33 |
34 | actual val isDisposed: Boolean
35 | get() = stableRef.isDisposed
36 | }
37 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (C) 2018 Touchlab, Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | kotlin.code.style=official
17 | org.gradle.jvmargs=-Xmx4g
18 | SONATYPE_HOST=DEFAULT
19 | RELEASE_SIGNING_ENABLED=true
20 |
21 | GROUP=co.touchlab
22 | VERSION_NAME=2.1.0
23 |
24 | POM_NAME=Stately
25 | POM_DESCRIPTION=Multithreaded Kotlin Multiplatform Utilities
26 | POM_URL=https://github.com/touchlab/Stately
27 | POM_SCM_URL=https://github.com/touchlab/Stately
28 | POM_SCM_CONNECTION=scm:git:git://github.com/touchlab/Stately.git
29 | POM_SCM_DEV_CONNECTION=scm:git:git://github.com/touchlab/Stately.git
30 |
31 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
32 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
33 | POM_LICENCE_DIST=repo
34 |
35 | POM_DEVELOPER_ID=kpgalligan
36 | POM_DEVELOPER_NAME=Kevin Galligan
37 | POM_DEVELOPER_ORG=Kevin Galligan
38 | POM_DEVELOPER_URL=https://touchlab.co/
39 |
40 | kotlin.js.ir.output.granularity=whole-program
41 |
42 | kotlin.mpp.enableCInteropCommonization=true
43 | kotlin.mpp.commonizerLogLevel=info
44 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 |
3 | agp = "8.3.0"
4 | buildConfig = "4.1.2"
5 | binaryCompatibilityValidator = "0.14.0"
6 | dokka = "1.9.0"
7 | touchlab-docusaurusosstemplate = "0.1.10"
8 | mavenPublish = "0.27.0"
9 | testHelp = "0.6.12"
10 | coroutines-test = "1.8.0"
11 | kotlin = "1.9.23"
12 |
13 | [libraries]
14 |
15 | testHelp = { module = "co.touchlab:testhelp", version.ref = "testHelp" }
16 | coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines-test" }
17 | # For convention-plugins
18 | kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
19 |
20 | [plugins]
21 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
22 | android-library = { id = "com.android.library", version.ref = "agp" }
23 | buildconfig = { id = "com.github.gmazzo.buildconfig", version.ref = "buildConfig" }
24 | binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidator" }
25 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
26 | touchlab-docusaurusosstemplate = { id = "co.touchlab.touchlabtools.docusaurusosstemplate", version.ref = "touchlab-docusaurusosstemplate" }
27 | mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" }
28 |
29 | [bundles]
30 |
31 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/touchlab/Stately/6ae0eb15df77dc4e44a2e1b6707afd5702cf0298/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Use the maximum available, or set MAX_FD != -1 to use that value.
89 | MAX_FD=maximum
90 |
91 | warn () {
92 | echo "$*"
93 | } >&2
94 |
95 | die () {
96 | echo
97 | echo "$*"
98 | echo
99 | exit 1
100 | } >&2
101 |
102 | # OS specific support (must be 'true' or 'false').
103 | cygwin=false
104 | msys=false
105 | darwin=false
106 | nonstop=false
107 | case "$( uname )" in #(
108 | CYGWIN* ) cygwin=true ;; #(
109 | Darwin* ) darwin=true ;; #(
110 | MSYS* | MINGW* ) msys=true ;; #(
111 | NONSTOP* ) nonstop=true ;;
112 | esac
113 |
114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
115 |
116 |
117 | # Determine the Java command to use to start the JVM.
118 | if [ -n "$JAVA_HOME" ] ; then
119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
120 | # IBM's JDK on AIX uses strange locations for the executables
121 | JAVACMD=$JAVA_HOME/jre/sh/java
122 | else
123 | JAVACMD=$JAVA_HOME/bin/java
124 | fi
125 | if [ ! -x "$JAVACMD" ] ; then
126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
127 |
128 | Please set the JAVA_HOME variable in your environment to match the
129 | location of your Java installation."
130 | fi
131 | else
132 | JAVACMD=java
133 | if ! command -v java >/dev/null 2>&1
134 | then
135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
136 |
137 | Please set the JAVA_HOME variable in your environment to match the
138 | location of your Java installation."
139 | fi
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
147 | # shellcheck disable=SC3045
148 | MAX_FD=$( ulimit -H -n ) ||
149 | warn "Could not query maximum file descriptor limit"
150 | esac
151 | case $MAX_FD in #(
152 | '' | soft) :;; #(
153 | *)
154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
155 | # shellcheck disable=SC3045
156 | ulimit -n "$MAX_FD" ||
157 | warn "Could not set maximum file descriptor limit to $MAX_FD"
158 | esac
159 | fi
160 |
161 | # Collect all arguments for the java command, stacking in reverse order:
162 | # * args from the command line
163 | # * the main class name
164 | # * -classpath
165 | # * -D...appname settings
166 | # * --module-path (only if needed)
167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
168 |
169 | # For Cygwin or MSYS, switch paths to Windows format before running java
170 | if "$cygwin" || "$msys" ; then
171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
173 |
174 | JAVACMD=$( cygpath --unix "$JAVACMD" )
175 |
176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
177 | for arg do
178 | if
179 | case $arg in #(
180 | -*) false ;; # don't mess with options #(
181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
182 | [ -e "$t" ] ;; #(
183 | *) false ;;
184 | esac
185 | then
186 | arg=$( cygpath --path --ignore --mixed "$arg" )
187 | fi
188 | # Roll the args list around exactly as many times as the number of
189 | # args, so each arg winds up back in the position where it started, but
190 | # possibly modified.
191 | #
192 | # NB: a `for` loop captures its iteration list before it begins, so
193 | # changing the positional parameters here affects neither the number of
194 | # iterations, nor the values presented in `arg`.
195 | shift # remove old arg
196 | set -- "$@" "$arg" # push replacement arg
197 | done
198 | fi
199 |
200 |
201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
203 |
204 | # Collect all arguments for the java command;
205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
206 | # shell script including quotes and variable substitutions, so put them in
207 | # double quotes to make sure that they get re-expanded; and
208 | # * put everything else in single quotes, so that it's not re-expanded.
209 |
210 | set -- \
211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
212 | -classpath "$CLASSPATH" \
213 | org.gradle.wrapper.GradleWrapperMain \
214 | "$@"
215 |
216 | # Stop when "xargs" is not available.
217 | if ! command -v xargs >/dev/null 2>&1
218 | then
219 | die "xargs is not available"
220 | fi
221 |
222 | # Use "xargs" to parse quoted args.
223 | #
224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
225 | #
226 | # In Bash we could simply go:
227 | #
228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
229 | # set -- "${ARGS[@]}" "$@"
230 | #
231 | # but POSIX shell has neither arrays nor command substitution, so instead we
232 | # post-process each arg (as a line of input to sed) to backslash-escape any
233 | # character that might be a shell metacharacter, then use eval to reverse
234 | # that process (while maintaining the separation between arguments), and wrap
235 | # the whole thing up as a single "set" statement.
236 | #
237 | # This will of course break if any of these variables contains a newline or
238 | # an unmatched quote.
239 | #
240 |
241 | eval "set -- $(
242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
243 | xargs -n1 |
244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
245 | tr '\n' ' '
246 | )" '"$@"'
247 |
248 | exec "$JAVACMD" "$@"
249 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 |
2 | include(":stately-strict")
3 | include(":stately-concurrency")
4 | include(":stately-concurrent-collections")
5 |
6 | // Deprecated modules
7 | include(":stately-common")
8 | include(":stately-collections")
9 | include(":stately-isolate")
10 | include(":stately-iso-collections")
11 |
12 | project(":stately-common").projectDir = File("deprecated/stately-common")
13 | project(":stately-collections").projectDir = File("deprecated/stately-collections")
14 | project(":stately-isolate").projectDir = File("deprecated/stately-isolate")
15 | project(":stately-iso-collections").projectDir = File("deprecated/stately-iso-collections")
16 |
17 | pluginManagement {
18 | includeBuild("convention-plugins")
19 | repositories {
20 | google()
21 | gradlePluginPortal()
22 | mavenCentral()
23 | }
24 | }
25 |
26 | dependencyResolutionManagement {
27 | @Suppress("UnstableApiUsage")
28 | repositories {
29 | mavenCentral()
30 | google()
31 | }
32 | }
--------------------------------------------------------------------------------
/stately-concurrency/api/stately-concurrency.api:
--------------------------------------------------------------------------------
1 | public final class co/touchlab/stately/concurrency/AtomicBoolean {
2 | public fun (Z)V
3 | public final fun compareAndSet (ZZ)Z
4 | public final fun getValue ()Z
5 | public final fun setValue (Z)V
6 | }
7 |
8 | public final class co/touchlab/stately/concurrency/AtomicIntKt {
9 | public static final fun getValue (Ljava/util/concurrent/atomic/AtomicInteger;)I
10 | public static final fun setValue (Ljava/util/concurrent/atomic/AtomicInteger;I)V
11 | }
12 |
13 | public final class co/touchlab/stately/concurrency/AtomicLongKt {
14 | public static final fun getValue (Ljava/util/concurrent/atomic/AtomicLong;)J
15 | public static final fun setValue (Ljava/util/concurrent/atomic/AtomicLong;J)V
16 | }
17 |
18 | public final class co/touchlab/stately/concurrency/AtomicReferenceKt {
19 | public static final fun getValue (Ljava/util/concurrent/atomic/AtomicReference;)Ljava/lang/Object;
20 | public static final fun setValue (Ljava/util/concurrent/atomic/AtomicReference;Ljava/lang/Object;)V
21 | }
22 |
23 | public final class co/touchlab/stately/concurrency/LockIntActual {
24 | public static final fun close (Ljava/util/concurrent/locks/ReentrantLock;)V
25 | }
26 |
27 | public final class co/touchlab/stately/concurrency/LockKt {
28 | public static final fun withLock (Ljava/util/concurrent/locks/ReentrantLock;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
29 | }
30 |
31 | public final class co/touchlab/stately/concurrency/SynchronizableKt {
32 | public static final fun synchronize (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
33 | }
34 |
35 | public final class co/touchlab/stately/concurrency/ThreadLocalKt {
36 | public static final fun getValue (Ljava/lang/ThreadLocal;)Ljava/lang/Object;
37 | public static final fun setValue (Ljava/lang/ThreadLocal;Ljava/lang/Object;)V
38 | }
39 |
40 | public final class co/touchlab/stately/concurrency/ThreadRef {
41 | public fun ()V
42 | public final fun same ()Z
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/stately-concurrency/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
2 |
3 | plugins {
4 | kotlin("multiplatform")
5 | id("com.vanniktech.maven.publish")
6 | id("kmp-setup")
7 | }
8 |
9 | val GROUP: String by project
10 | val VERSION_NAME: String by project
11 |
12 | group = GROUP
13 | version = VERSION_NAME
14 |
15 | kotlin {
16 | sourceSets {
17 | commonMain.dependencies {
18 | implementation(project(":stately-strict"))
19 | }
20 | commonTest.dependencies {
21 | implementation(kotlin("test"))
22 | implementation(libs.testHelp)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/stately-concurrency/src/AndroidNativeMain/kotlin/co/touchlab/stately/concurrency/Lock.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.concurrency
2 |
3 | import co.touchlab.stately.strict.maybeFreeze
4 | import kotlinx.cinterop.Arena
5 | import kotlinx.cinterop.UnsafeNumber
6 | import kotlinx.cinterop.alloc
7 | import kotlinx.cinterop.ptr
8 | import platform.posix.PTHREAD_MUTEX_RECURSIVE
9 | import platform.posix.pthread_mutex_destroy
10 | import platform.posix.pthread_mutex_init
11 | import platform.posix.pthread_mutex_lock
12 | import platform.posix.pthread_mutex_t
13 | import platform.posix.pthread_mutex_trylock
14 | import platform.posix.pthread_mutex_unlock
15 | import platform.posix.pthread_mutexattr_destroy
16 | import platform.posix.pthread_mutexattr_init
17 | import platform.posix.pthread_mutexattr_settype
18 | import platform.posix.pthread_mutexattr_tVar
19 |
20 | /**
21 | * A simple lock.
22 | * Implementations of this class should be re-entrant.
23 | */
24 | @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class, UnsafeNumber::class)
25 | actual class Lock actual constructor() {
26 | private val arena = Arena()
27 | private val attr = arena.alloc()
28 | private val mutex = arena.alloc()
29 |
30 | init {
31 | pthread_mutexattr_init(attr.ptr)
32 | pthread_mutexattr_settype(attr.ptr, PTHREAD_MUTEX_RECURSIVE.toInt())
33 | pthread_mutex_init(mutex.ptr, attr.ptr)
34 | maybeFreeze()
35 | }
36 |
37 | actual fun lock() {
38 | pthread_mutex_lock(mutex.ptr)
39 | }
40 |
41 | actual fun unlock() {
42 | pthread_mutex_unlock(mutex.ptr)
43 | }
44 |
45 | actual fun tryLock(): Boolean = pthread_mutex_trylock(mutex.ptr) == 0
46 |
47 | fun internalClose() {
48 | pthread_mutex_destroy(mutex.ptr)
49 | pthread_mutexattr_destroy(attr.ptr)
50 | arena.clear()
51 | }
52 | }
53 |
54 | actual inline fun Lock.close() {
55 | internalClose()
56 | }
57 |
--------------------------------------------------------------------------------
/stately-concurrency/src/appleMain/kotlin/co/touchlab/stately/concurrency/Lock.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.concurrency
18 |
19 | import platform.Foundation.NSRecursiveLock
20 |
21 | actual typealias Lock = NSRecursiveLock
22 |
23 | @Suppress("NOTHING_TO_INLINE")
24 | actual inline fun Lock.close() {}
25 |
--------------------------------------------------------------------------------
/stately-concurrency/src/commonMain/kotlin/co/touchlab/stately/concurrency/AtomicBoolean.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.concurrency
18 |
19 | /**
20 | * Multiplatform AtomicBoolean implementation
21 | */
22 | class AtomicBoolean(value_: Boolean) {
23 | private val atom = AtomicInt(boolToInt(value_))
24 | var value: Boolean
25 | get() = atom.value != 0
26 | set(value) {
27 | atom.value = boolToInt(value)
28 | }
29 |
30 | fun compareAndSet(expected: Boolean, new: Boolean): Boolean =
31 | atom.compareAndSet(boolToInt(expected), boolToInt(new))
32 |
33 | private fun boolToInt(b: Boolean): Int = if (b) {
34 | 1
35 | } else {
36 | 0
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/stately-concurrency/src/commonMain/kotlin/co/touchlab/stately/concurrency/AtomicInt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.concurrency
18 |
19 | /**
20 | * Multiplatform AtomicInt implementation
21 | */
22 | expect class AtomicInt(initialValue: Int) {
23 | fun get(): Int
24 | fun set(newValue: Int)
25 | fun incrementAndGet(): Int
26 | fun decrementAndGet(): Int
27 |
28 | fun addAndGet(delta: Int): Int
29 | fun compareAndSet(expected: Int, new: Int): Boolean
30 | }
31 |
32 | var AtomicInt.value
33 | get() = get()
34 | set(value) {
35 | set(value)
36 | }
37 |
--------------------------------------------------------------------------------
/stately-concurrency/src/commonMain/kotlin/co/touchlab/stately/concurrency/AtomicLong.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.concurrency
18 |
19 | /**
20 | * Multiplatform AtomicLong implementation
21 | */
22 | expect class AtomicLong(initialValue: Long) {
23 |
24 | fun get(): Long
25 | fun set(newValue: Long)
26 | fun incrementAndGet(): Long
27 | fun decrementAndGet(): Long
28 |
29 | fun addAndGet(delta: Long): Long
30 | fun compareAndSet(expected: Long, new: Long): Boolean
31 | }
32 |
33 | var AtomicLong.value
34 | get() = get()
35 | set(value) {
36 | set(value)
37 | }
38 |
--------------------------------------------------------------------------------
/stately-concurrency/src/commonMain/kotlin/co/touchlab/stately/concurrency/AtomicReference.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.concurrency
18 |
19 | /**
20 | * Multiplatform AtomicReference implementation
21 | */
22 | expect class AtomicReference(initialValue: V) {
23 | fun get(): V
24 | fun set(value_: V)
25 |
26 | /**
27 | * Compare current value with expected and set to new if they're the same. Note, 'compare' is checking
28 | * the actual object id, not 'equals'.
29 | */
30 | fun compareAndSet(expected: V, new: V): Boolean
31 | }
32 |
33 | var AtomicReference.value: T
34 | get() = get()
35 | set(value) {
36 | set(value)
37 | }
38 |
--------------------------------------------------------------------------------
/stately-concurrency/src/commonMain/kotlin/co/touchlab/stately/concurrency/Functions.kt:
--------------------------------------------------------------------------------
1 | package co.touchlab.stately.concurrency
2 |
3 | expect open class Synchronizable()
4 |
5 | expect inline fun Synchronizable.synchronize(noinline block: () -> R): R
--------------------------------------------------------------------------------
/stately-concurrency/src/commonMain/kotlin/co/touchlab/stately/concurrency/Lock.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018 Touchlab, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package co.touchlab.stately.concurrency
18 |
19 | /**
20 | * A simple lock.
21 | * Implementations of this class should be re-entrant.
22 | */
23 | expect class Lock() {
24 | fun lock()
25 | fun unlock()
26 | fun tryLock(): Boolean
27 | }
28 |
29 | expect inline fun Lock.close()
30 |
31 | inline fun Lock.withLock(block: () -> T): T {
32 | lock()
33 | try {
34 | return block()
35 | } finally {
36 | unlock()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/stately-concurrency/src/commonMain/kotlin/co/touchlab/stately/concurrency/ThreadLocal.kt:
--------------------------------------------------------------------------------
1 | // ktlint-disable filename
2 | /*
3 | * Copyright (C) 2018 Touchlab, Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package co.touchlab.stately.concurrency
19 |
20 | expect open class ThreadLocalRef() {
21 | fun get(): T?
22 | fun set(value: T?)
23 | fun remove()
24 | }
25 |
26 | var