├── .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 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 33 | 34 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/ktlint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | ![Image of Kevin](https://avatars.githubusercontent.com/u/68384?s=140&v=4) 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 | ![Image of Kevin](https://avatars.githubusercontent.com/u/68384?s=140&v=4) 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 ThreadLocalRef.value: T? 27 | get() = get() 28 | set(value) { 29 | set(value) 30 | } 31 | -------------------------------------------------------------------------------- /stately-concurrency/src/commonMain/kotlin/co/touchlab/stately/concurrency/ThreadRef.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.concurrency 2 | 3 | expect class ThreadRef() { 4 | fun same(): Boolean 5 | } 6 | -------------------------------------------------------------------------------- /stately-concurrency/src/commonTest/kotlin/co/touchlab/stately/concurrency/Atomics.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 co.touchlab.stately.strict.maybeFreeze 20 | import kotlin.test.Test 21 | import kotlin.test.assertEquals 22 | import kotlin.test.assertFalse 23 | import kotlin.test.assertTrue 24 | 25 | class Atomics { 26 | @Test 27 | fun atomicBoolean() { 28 | val ab = AtomicBoolean(false) 29 | assertFalse(ab.value) 30 | ab.value = true 31 | assertTrue(ab.value) 32 | 33 | assertFalse(ab.compareAndSet(false, false)) 34 | assertTrue(ab.value) 35 | assertTrue(ab.compareAndSet(true, false)) 36 | assertFalse(ab.value) 37 | } 38 | 39 | @Test 40 | fun atomicReference() { 41 | val ref = AtomicReference(ATData("asdf")).maybeFreeze() 42 | assertEquals(ref.value, ATData("asdf")) 43 | val actualData = ATData("qwert") 44 | ref.value = actualData 45 | assertEquals(ref.value, ATData("qwert")) 46 | assertFalse(ref.compareAndSet(ATData("rrr"), ATData("ttt"))) 47 | assertEquals(ref.value, ATData("qwert")) 48 | assertFalse(ref.compareAndSet(ATData("qwert"), ATData("ttt"))) 49 | assertTrue(ref.compareAndSet(actualData, ATData("ttt"))) 50 | assertEquals(ref.value, ATData("ttt")) 51 | } 52 | 53 | @Test 54 | fun atomicInt() { 55 | val ref = AtomicInt(22) 56 | assertEquals(22, ref.value) 57 | 58 | ref.incrementAndGet() 59 | 60 | assertEquals(23, ref.value) 61 | 62 | ref.incrementAndGet() 63 | ref.incrementAndGet() 64 | 65 | assertEquals(25, ref.value) 66 | 67 | ref.decrementAndGet() 68 | 69 | assertEquals(24, ref.value) 70 | 71 | assertEquals(29, ref.addAndGet(5)) 72 | 73 | assertEquals(29, ref.value) 74 | 75 | assertFalse(ref.compareAndSet(28, 33)) 76 | 77 | assertEquals(29, ref.value) 78 | 79 | assertTrue(ref.compareAndSet(29, 33)) 80 | 81 | assertEquals(33, ref.value) 82 | } 83 | 84 | @Test 85 | fun atomicLong() { 86 | val ref = AtomicLong(22) 87 | assertEquals(22, ref.value) 88 | 89 | ref.incrementAndGet() 90 | 91 | assertEquals(23, ref.value) 92 | 93 | ref.incrementAndGet() 94 | ref.incrementAndGet() 95 | 96 | assertEquals(25, ref.value) 97 | 98 | ref.decrementAndGet() 99 | 100 | assertEquals(24, ref.value) 101 | 102 | assertEquals(29, ref.addAndGet(5)) 103 | 104 | assertEquals(29, ref.value) 105 | 106 | assertFalse(ref.compareAndSet(28, 33)) 107 | 108 | assertEquals(29, ref.value) 109 | 110 | assertTrue(ref.compareAndSet(29, 33)) 111 | 112 | assertEquals(33, ref.value) 113 | } 114 | 115 | @Test 116 | fun atomicCyclic() { 117 | val atList = mutableListOf>() 118 | for (i in 0 until 50) { 119 | val c = CycleData(null) 120 | val c2 = CycleData2(c) 121 | val c3 = CycleData3(c2) 122 | c.b = c3 123 | 124 | val at = AtomicReference(c.maybeFreeze()) 125 | atList.add(at) 126 | } 127 | 128 | atList.maybeFreeze() 129 | } 130 | } 131 | 132 | data class ATData(val s: String) 133 | 134 | data class CycleData(var b: CycleData3?) 135 | data class CycleData2(val b: CycleData) 136 | data class CycleData3(val b: CycleData2) 137 | -------------------------------------------------------------------------------- /stately-concurrency/src/commonTest/kotlin/co/touchlab/stately/concurrency/LockTest.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.concurrency 2 | 3 | import co.touchlab.testhelp.concurrency.ThreadOperations 4 | import co.touchlab.testhelp.concurrency.sleep 5 | import co.touchlab.testhelp.isMultithreaded 6 | import kotlin.test.Test 7 | import kotlin.test.assertEquals 8 | 9 | class LockTest { 10 | 11 | /** 12 | * Just making sure locks are created. Not really testing the locks. 13 | */ 14 | @Test 15 | fun lockWorks() { 16 | // Don't care about JS 17 | if (!isMultithreaded) { 18 | return 19 | } 20 | 21 | val lock = Lock() 22 | val aint = AtomicInt(0) 23 | 24 | val ops = ThreadOperations { } 25 | ops.exe { 26 | lock.withLock { 27 | sleep(3000) 28 | aint.value = 1 29 | } 30 | } 31 | 32 | ops.run(1) 33 | sleep(1000) 34 | lock.withLock { 35 | assertEquals(1, aint.value) 36 | aint.value = 2 37 | } 38 | 39 | sleep(3000) 40 | 41 | lock.withLock { 42 | assertEquals(2, aint.value) 43 | } 44 | 45 | lock.close() 46 | } 47 | 48 | @Test 49 | fun lockReentrant() { 50 | // Don't care about JS 51 | if (!isMultithreaded) { 52 | return 53 | } 54 | 55 | val lock = Lock() 56 | 57 | lock.withLock { 58 | lock.lock() 59 | sleep(1000) 60 | lock.unlock() 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /stately-concurrency/src/commonTest/kotlin/co/touchlab/stately/concurrency/ThreadRefTest.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.concurrency 2 | 3 | import co.touchlab.testhelp.concurrency.ThreadOperations 4 | import co.touchlab.testhelp.isMultithreaded 5 | import kotlin.test.Test 6 | import kotlin.test.assertFalse 7 | import kotlin.test.assertTrue 8 | 9 | class ThreadRefTest { 10 | @Test 11 | fun threadRefTest() { 12 | // Don't care about JS 13 | if (!isMultithreaded) { 14 | return 15 | } 16 | 17 | val ref = ThreadRef() 18 | assertTrue(ref.same()) 19 | val ops = ThreadOperations { } 20 | ops.exe { 21 | assertFalse(ref.same()) 22 | } 23 | 24 | ops.run(1) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /stately-concurrency/src/jsAndWasmJsMain/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 | actual class AtomicInt actual constructor(initialValue: Int) { 23 | private var internalValue: Int = initialValue 24 | 25 | actual fun addAndGet(delta: Int): Int { 26 | return (internalValue + delta).also { 27 | internalValue = it 28 | } 29 | } 30 | 31 | actual fun compareAndSet(expected: Int, new: Int): Boolean { 32 | return if (expected == internalValue) { 33 | internalValue = new 34 | true 35 | } else { 36 | false 37 | } 38 | } 39 | 40 | actual fun get(): Int = internalValue 41 | 42 | actual fun set(newValue: Int) { 43 | internalValue = newValue 44 | } 45 | 46 | actual fun incrementAndGet(): Int = addAndGet(1) 47 | 48 | actual fun decrementAndGet(): Int = addAndGet(-1) 49 | } 50 | -------------------------------------------------------------------------------- /stately-concurrency/src/jsAndWasmJsMain/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 | actual class AtomicLong actual constructor(initialValue: Long) { 23 | private var internalValue: Long = initialValue 24 | 25 | actual fun addAndGet(delta: Long): Long { 26 | return (internalValue + delta).also { 27 | internalValue = it 28 | } 29 | } 30 | 31 | actual fun compareAndSet(expected: Long, new: Long): Boolean { 32 | return if (expected == internalValue) { 33 | internalValue = new 34 | true 35 | } else { 36 | false 37 | } 38 | } 39 | 40 | actual fun get(): Long = internalValue 41 | 42 | actual fun set(newValue: Long) { 43 | internalValue = newValue 44 | } 45 | 46 | actual fun incrementAndGet(): Long = addAndGet(1) 47 | 48 | actual fun decrementAndGet(): Long = addAndGet(-1) 49 | } 50 | -------------------------------------------------------------------------------- /stately-concurrency/src/jsAndWasmJsMain/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 | actual class AtomicReference actual constructor(initialValue: V) { 23 | private var internalValue: V = initialValue 24 | 25 | /** 26 | * Compare current value with expected and set to new if they're the same. Note, 'compare' is checking 27 | * the actual object id, not 'equals'. 28 | */ 29 | actual fun compareAndSet(expected: V, new: V): Boolean { 30 | return if (expected === internalValue) { 31 | internalValue = new 32 | true 33 | } else { 34 | false 35 | } 36 | } 37 | 38 | actual fun get(): V = internalValue 39 | 40 | actual fun set(value_: V) { 41 | internalValue = value_ 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /stately-concurrency/src/jsAndWasmJsMain/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 | * Reentrant locks aren't super exciting in a single threaded world. 21 | */ 22 | actual class Lock actual constructor() { 23 | actual fun lock() {} 24 | actual fun unlock() {} 25 | actual fun tryLock(): Boolean = true 26 | } 27 | 28 | @Suppress("NOTHING_TO_INLINE") 29 | actual inline fun Lock.close() {} 30 | -------------------------------------------------------------------------------- /stately-concurrency/src/jsAndWasmJsMain/kotlin/co/touchlab/stately/concurrency/Synchronizable.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.concurrency 2 | 3 | actual typealias Synchronizable = Any 4 | 5 | actual inline fun Synchronizable.synchronize(noinline block: () -> R): R = block() -------------------------------------------------------------------------------- /stately-concurrency/src/jsAndWasmJsMain/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 | actual open class ThreadLocalRef actual constructor() { 21 | private var localValue: T? = null 22 | 23 | actual fun remove() { 24 | value = null 25 | } 26 | 27 | actual fun get(): T? = localValue 28 | 29 | actual fun set(value: T?) { 30 | localValue = value 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /stately-concurrency/src/jsAndWasmJsMain/kotlin/co/touchlab/stately/concurrency/ThreadRef.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.concurrency 2 | 3 | actual class ThreadRef actual constructor() { 4 | actual fun same(): Boolean = true 5 | } 6 | -------------------------------------------------------------------------------- /stately-concurrency/src/jvmMain/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 | @file:JvmName("AtomicIntActual") 18 | 19 | package co.touchlab.stately.concurrency 20 | 21 | import java.util.concurrent.atomic.AtomicInteger 22 | 23 | actual typealias AtomicInt = AtomicInteger 24 | -------------------------------------------------------------------------------- /stately-concurrency/src/jvmMain/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 | @file:JvmName("AtomicLongActual") 18 | 19 | package co.touchlab.stately.concurrency 20 | 21 | actual typealias AtomicLong = java.util.concurrent.atomic.AtomicLong 22 | -------------------------------------------------------------------------------- /stately-concurrency/src/jvmMain/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 | @file:JvmName("AtomicReferenceActual") 17 | 18 | package co.touchlab.stately.concurrency 19 | 20 | import java.util.concurrent.atomic.AtomicReference 21 | 22 | actual typealias AtomicReference = AtomicReference 23 | -------------------------------------------------------------------------------- /stately-concurrency/src/jvmMain/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 | @file:JvmName("LockIntActual") 17 | 18 | package co.touchlab.stately.concurrency 19 | 20 | actual typealias Lock = java.util.concurrent.locks.ReentrantLock 21 | 22 | @Suppress("NOTHING_TO_INLINE") 23 | actual inline fun Lock.close() {} 24 | -------------------------------------------------------------------------------- /stately-concurrency/src/jvmMain/kotlin/co/touchlab/stately/concurrency/Synchronizable.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.concurrency 2 | 3 | actual typealias Synchronizable = Any 4 | 5 | actual inline fun Synchronizable.synchronize(noinline block: () -> R): R = synchronized(this, block) -------------------------------------------------------------------------------- /stately-concurrency/src/jvmMain/kotlin/co/touchlab/stately/concurrency/ThreadLocalJVM.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 | actual typealias ThreadLocalRef = ThreadLocal 21 | -------------------------------------------------------------------------------- /stately-concurrency/src/jvmMain/kotlin/co/touchlab/stately/concurrency/ThreadRef.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.concurrency 2 | 3 | actual class ThreadRef actual constructor() { 4 | private val threadRef = Thread.currentThread().id 5 | 6 | actual fun same(): Boolean = threadRef == Thread.currentThread().id 7 | } 8 | -------------------------------------------------------------------------------- /stately-concurrency/src/linuxMain/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.alloc 6 | import kotlinx.cinterop.ptr 7 | import platform.posix.PTHREAD_MUTEX_RECURSIVE 8 | import platform.posix.pthread_mutex_destroy 9 | import platform.posix.pthread_mutex_init 10 | import platform.posix.pthread_mutex_lock 11 | import platform.posix.pthread_mutex_t 12 | import platform.posix.pthread_mutex_trylock 13 | import platform.posix.pthread_mutex_unlock 14 | import platform.posix.pthread_mutexattr_destroy 15 | import platform.posix.pthread_mutexattr_init 16 | import platform.posix.pthread_mutexattr_settype 17 | import platform.posix.pthread_mutexattr_t 18 | 19 | /** 20 | * A simple lock. 21 | * Implementations of this class should be re-entrant. 22 | */ 23 | @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) 24 | actual class Lock actual constructor() { 25 | private val arena = Arena() 26 | private val attr = arena.alloc() 27 | private val mutex = arena.alloc() 28 | 29 | init { 30 | pthread_mutexattr_init(attr.ptr) 31 | pthread_mutexattr_settype(attr.ptr, PTHREAD_MUTEX_RECURSIVE.toInt()) 32 | pthread_mutex_init(mutex.ptr, attr.ptr) 33 | maybeFreeze() 34 | } 35 | 36 | actual fun lock() { 37 | pthread_mutex_lock(mutex.ptr) 38 | } 39 | 40 | actual fun unlock() { 41 | pthread_mutex_unlock(mutex.ptr) 42 | } 43 | 44 | actual fun tryLock(): Boolean = pthread_mutex_trylock(mutex.ptr) == 0 45 | 46 | fun internalClose() { 47 | pthread_mutex_destroy(mutex.ptr) 48 | pthread_mutexattr_destroy(attr.ptr) 49 | arena.clear() 50 | } 51 | } 52 | 53 | actual inline fun Lock.close() { 54 | internalClose() 55 | } 56 | -------------------------------------------------------------------------------- /stately-concurrency/src/mingwMain/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.alloc 6 | import kotlinx.cinterop.ptr 7 | import platform.posix.PTHREAD_MUTEX_RECURSIVE 8 | import platform.posix.pthread_mutex_destroy 9 | import platform.posix.pthread_mutex_init 10 | import platform.posix.pthread_mutex_lock 11 | import platform.posix.pthread_mutex_tVar 12 | import platform.posix.pthread_mutex_trylock 13 | import platform.posix.pthread_mutex_unlock 14 | import platform.posix.pthread_mutexattr_destroy 15 | import platform.posix.pthread_mutexattr_init 16 | import platform.posix.pthread_mutexattr_settype 17 | import platform.posix.pthread_mutexattr_tVar 18 | 19 | /** 20 | * A simple lock. 21 | * Implementations of this class should be re-entrant. 22 | */ 23 | @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) 24 | actual class Lock actual constructor() { 25 | private val arena = Arena() 26 | private val attr = arena.alloc() 27 | private val mutex = arena.alloc() 28 | 29 | init { 30 | pthread_mutexattr_init(attr.ptr) 31 | pthread_mutexattr_settype(attr.ptr, PTHREAD_MUTEX_RECURSIVE.toInt()) 32 | pthread_mutex_init(mutex.ptr, attr.ptr) 33 | maybeFreeze() 34 | } 35 | 36 | actual fun lock() { 37 | pthread_mutex_lock(mutex.ptr) 38 | } 39 | 40 | actual fun unlock() { 41 | pthread_mutex_unlock(mutex.ptr) 42 | } 43 | 44 | actual fun tryLock(): Boolean = pthread_mutex_trylock(mutex.ptr) == 0 45 | 46 | fun internalClose() { 47 | pthread_mutex_destroy(mutex.ptr) 48 | pthread_mutexattr_destroy(attr.ptr) 49 | arena.clear() 50 | } 51 | } 52 | 53 | actual inline fun Lock.close() { 54 | internalClose() 55 | } 56 | -------------------------------------------------------------------------------- /stately-concurrency/src/nativeMain/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 | import kotlin.concurrent.AtomicInt 20 | 21 | actual class AtomicInt actual constructor(initialValue: Int) { 22 | private val atom = AtomicInt(initialValue) 23 | 24 | actual fun get(): Int = atom.value 25 | 26 | actual fun set(newValue: Int) { 27 | atom.value = newValue 28 | } 29 | 30 | actual fun incrementAndGet(): Int = atom.addAndGet(1) 31 | 32 | actual fun decrementAndGet(): Int = atom.addAndGet(-1) 33 | 34 | actual fun addAndGet(delta: Int): Int = atom.addAndGet(delta) 35 | 36 | actual fun compareAndSet(expected: Int, new: Int): Boolean = atom.compareAndSet(expected, new) 37 | } 38 | -------------------------------------------------------------------------------- /stately-concurrency/src/nativeMain/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 | import kotlin.concurrent.AtomicLong 20 | 21 | actual class AtomicLong actual constructor(initialValue: Long) { 22 | private val atom = AtomicLong(initialValue) 23 | 24 | actual fun get(): Long = atom.value 25 | 26 | actual fun set(newValue: Long) { 27 | atom.value = newValue 28 | } 29 | 30 | actual fun incrementAndGet(): Long = atom.addAndGet(1) 31 | 32 | actual fun decrementAndGet(): Long = atom.addAndGet(-1) 33 | 34 | actual fun addAndGet(delta: Long): Long = atom.addAndGet(delta) 35 | 36 | actual fun compareAndSet(expected: Long, new: Long): Boolean = atom.compareAndSet(expected, new) 37 | } 38 | -------------------------------------------------------------------------------- /stately-concurrency/src/nativeMain/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 | import co.touchlab.stately.strict.maybeFreeze 20 | import kotlin.concurrent.AtomicReference 21 | 22 | // actual typealias AtomicReference = AtomicReference 23 | actual class AtomicReference actual constructor(initialValue: V) { 24 | private val atom = AtomicReference(initialValue.maybeFreeze()) 25 | actual fun get(): V = atom.value 26 | 27 | actual fun set(value_: V) { 28 | atom.value = value_.maybeFreeze() 29 | } 30 | 31 | /** 32 | * Compare current value with expected and set to new if they're the same. Note, 'compare' is checking 33 | * the actual object id, not 'equals'. 34 | */ 35 | actual fun compareAndSet(expected: V, new: V): Boolean = atom.compareAndSet(expected, new.maybeFreeze()) 36 | } 37 | -------------------------------------------------------------------------------- /stately-concurrency/src/nativeMain/kotlin/co/touchlab/stately/concurrency/Functions.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.concurrency 2 | 3 | actual open class Synchronizable(private val _lock: Lock) { 4 | actual constructor() : this(Lock()) 5 | 6 | fun runSynchronized(block: () -> R): R = _lock.withLock(block) 7 | } 8 | 9 | actual inline fun Synchronizable.synchronize(noinline block: () -> R): R = runSynchronized(block) -------------------------------------------------------------------------------- /stately-concurrency/src/nativeMain/kotlin/co/touchlab/stately/concurrency/GuardedStableRef.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.concurrency 2 | 3 | import kotlinx.cinterop.StableRef 4 | 5 | @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) 6 | class GuardedStableRef(t: T) { 7 | private val stableRef: StableRef = StableRef.create(t) 8 | private val threadRef = ThreadRef() 9 | internal val disposed = AtomicBoolean(false) 10 | 11 | public val isDisposed 12 | get() = disposed.value 13 | 14 | val state: T 15 | get() { 16 | checkStateAccessValid() 17 | return stableRef.get() 18 | } 19 | 20 | fun dispose() { 21 | checkStateAccessValid() 22 | stableRef.dispose() 23 | disposed.value = true 24 | } 25 | 26 | private fun checkStateAccessValid() { 27 | if (!threadRef.same()) { 28 | throw IllegalStateException("StableRef can only be accessed from the thread it was created with") 29 | } 30 | 31 | if (disposed.value) { 32 | throw IllegalStateException("StableRef already disposed") 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /stately-concurrency/src/nativeMain/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 | actual open class ThreadLocalRef actual constructor() { 21 | private val threadLocalId = ThreadLocalIdCounter.nextThreadLocalId() 22 | 23 | actual fun remove() { 24 | ThreadLocalState.threadLocalMap.remove(threadLocalId) 25 | } 26 | 27 | actual fun get(): T? { 28 | return if (ThreadLocalState.threadLocalMap.containsKey(threadLocalId)) { 29 | ThreadLocalState.threadLocalMap.get(threadLocalId) as T 30 | } else { 31 | null 32 | } 33 | } 34 | 35 | actual fun set(value: T?) { 36 | if (value == null) { 37 | remove() 38 | } else { 39 | ThreadLocalState.threadLocalMap.put(threadLocalId, value) 40 | } 41 | } 42 | } 43 | 44 | @ThreadLocal 45 | private object ThreadLocalState { 46 | val threadLocalMap = HashMap() 47 | } 48 | 49 | private object ThreadLocalIdCounter { 50 | val threadLocalId = AtomicInt(0) 51 | fun nextThreadLocalId(): Int = threadLocalId.addAndGet(1) 52 | } 53 | -------------------------------------------------------------------------------- /stately-concurrency/src/nativeMain/kotlin/co/touchlab/stately/concurrency/ThreadRef.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.concurrency 2 | 3 | import kotlin.concurrent.AtomicInt 4 | 5 | @ThreadLocal 6 | private var localThreadId: Int = 0 7 | 8 | @SharedImmutable 9 | private val threadIdCounter = AtomicInt(1) 10 | 11 | private fun currentThreadId(): Int { 12 | if (localThreadId == 0) { 13 | localThreadId = threadIdCounter.addAndGet(1) 14 | } 15 | return localThreadId 16 | } 17 | 18 | actual class ThreadRef actual constructor() { 19 | private val threadId: Int = currentThreadId() 20 | 21 | actual fun same(): Boolean = threadId == currentThreadId() 22 | } 23 | -------------------------------------------------------------------------------- /stately-concurrency/src/nativeTest/kotlin/co/touchlab/stately/concurrency/GuardedStableRefTest.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.concurrency 2 | 3 | import kotlin.native.concurrent.TransferMode 4 | import kotlin.native.concurrent.Worker 5 | import kotlin.native.concurrent.freeze 6 | import kotlin.test.AfterTest 7 | import kotlin.test.BeforeTest 8 | import kotlin.test.Test 9 | import kotlin.test.assertEquals 10 | import kotlin.test.assertFails 11 | import kotlin.test.assertNotNull 12 | 13 | class GuardedStableRefTest { 14 | 15 | var _w: Worker? = null 16 | val w: Worker 17 | get() = _w!! 18 | 19 | lateinit var g: GuardedStableRef 20 | 21 | @BeforeTest 22 | fun setUp() { 23 | _w = Worker.start() 24 | g = w.execute(TransferMode.SAFE, {}) { 25 | GuardedStableRef(MutData(1)) 26 | }.result.freeze() 27 | } 28 | 29 | @AfterTest 30 | fun tearDown() { 31 | w.execute(TransferMode.SAFE, { g }) { 32 | if (!it.disposed.value) { 33 | it.dispose() 34 | } 35 | } 36 | w.requestTermination().result 37 | } 38 | 39 | @Test 40 | fun wrongThreadAccessFails() { 41 | assertFails { g.state } 42 | } 43 | 44 | @Test 45 | fun wrongThreadDisposeFails() { 46 | assertFails { g.dispose() } 47 | } 48 | 49 | @Test 50 | fun mutateStateInThread() { 51 | val newMut = w.execute(TransferMode.SAFE, { g.freeze() }) { 52 | val mut = it.state 53 | mut.c = 42 54 | 55 | mut.copy() 56 | }.result 57 | 58 | assertEquals(42, newMut.c) 59 | } 60 | 61 | @Test 62 | fun disposeInThread() { 63 | w.execute(TransferMode.SAFE, { g }) { 64 | it.dispose() 65 | }.result 66 | } 67 | 68 | @Test 69 | fun alreadyDisposedException() { 70 | val exceptionResult = w.execute(TransferMode.SAFE, { g }) { 71 | it.dispose() 72 | 73 | try { 74 | it.dispose() 75 | } catch (e: Exception) { 76 | return@execute e 77 | } 78 | return@execute null 79 | }.result 80 | 81 | assertNotNull(exceptionResult) 82 | } 83 | 84 | data class MutData(var c: Int) 85 | } 86 | -------------------------------------------------------------------------------- /stately-concurrent-collections/api/stately-concurrent-collections.api: -------------------------------------------------------------------------------- 1 | public class co/touchlab/stately/collections/ConcurrentMutableCollection : java/util/Collection, kotlin/jvm/internal/markers/KMutableCollection { 2 | public fun add (Ljava/lang/Object;)Z 3 | public fun addAll (Ljava/util/Collection;)Z 4 | public final fun blockCollection (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; 5 | public fun clear ()V 6 | public fun contains (Ljava/lang/Object;)Z 7 | public fun containsAll (Ljava/util/Collection;)Z 8 | public fun getSize ()I 9 | public fun isEmpty ()Z 10 | public fun iterator ()Ljava/util/Iterator; 11 | public fun remove (Ljava/lang/Object;)Z 12 | public fun removeAll (Ljava/util/Collection;)Z 13 | public fun retainAll (Ljava/util/Collection;)Z 14 | public final fun size ()I 15 | public fun toArray ()[Ljava/lang/Object; 16 | public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; 17 | } 18 | 19 | public final class co/touchlab/stately/collections/ConcurrentMutableList : co/touchlab/stately/collections/ConcurrentMutableCollection, java/util/List, kotlin/jvm/internal/markers/KMutableList { 20 | public fun ()V 21 | public fun add (ILjava/lang/Object;)V 22 | public fun addAll (ILjava/util/Collection;)Z 23 | public final fun block (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; 24 | public fun get (I)Ljava/lang/Object; 25 | public fun indexOf (Ljava/lang/Object;)I 26 | public fun lastIndexOf (Ljava/lang/Object;)I 27 | public fun listIterator ()Ljava/util/ListIterator; 28 | public fun listIterator (I)Ljava/util/ListIterator; 29 | public final fun remove (I)Ljava/lang/Object; 30 | public fun removeAt (I)Ljava/lang/Object; 31 | public fun set (ILjava/lang/Object;)Ljava/lang/Object; 32 | public fun subList (II)Ljava/util/List; 33 | } 34 | 35 | public final class co/touchlab/stately/collections/ConcurrentMutableMap : java/util/Map, kotlin/jvm/internal/markers/KMutableMap { 36 | public fun ()V 37 | public final fun block (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; 38 | public fun clear ()V 39 | public fun containsKey (Ljava/lang/Object;)Z 40 | public fun containsValue (Ljava/lang/Object;)Z 41 | public final fun entrySet ()Ljava/util/Set; 42 | public fun get (Ljava/lang/Object;)Ljava/lang/Object; 43 | public fun getEntries ()Ljava/util/Set; 44 | public fun getKeys ()Ljava/util/Set; 45 | public fun getSize ()I 46 | public fun getValues ()Ljava/util/Collection; 47 | public fun isEmpty ()Z 48 | public final fun keySet ()Ljava/util/Set; 49 | public fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; 50 | public fun putAll (Ljava/util/Map;)V 51 | public fun remove (Ljava/lang/Object;)Ljava/lang/Object; 52 | public final fun safeComputeIfAbsent (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; 53 | public final fun size ()I 54 | public final fun values ()Ljava/util/Collection; 55 | } 56 | 57 | public final class co/touchlab/stately/collections/ConcurrentMutableSet : co/touchlab/stately/collections/ConcurrentMutableCollection, java/util/Set, kotlin/jvm/internal/markers/KMutableSet { 58 | public fun ()V 59 | public final fun block (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; 60 | } 61 | 62 | -------------------------------------------------------------------------------- /stately-concurrent-collections/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("PropertyName") 2 | 3 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi 4 | 5 | plugins { 6 | kotlin("multiplatform") 7 | id("com.vanniktech.maven.publish") 8 | id("kmp-setup") 9 | } 10 | 11 | val GROUP: String by project 12 | val VERSION_NAME: String by project 13 | 14 | group = GROUP 15 | version = VERSION_NAME 16 | 17 | kotlin { 18 | sourceSets { 19 | commonMain.dependencies { 20 | api(project(":stately-concurrency")) 21 | } 22 | commonTest.dependencies { 23 | implementation(kotlin("test")) 24 | implementation(libs.testHelp) 25 | implementation(libs.coroutines.test) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /stately-concurrent-collections/src/commonMain/kotlin/co/touchlab/stately/collections/ConcurrentMutableCollection.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.collections 2 | 3 | import co.touchlab.stately.concurrency.Synchronizable 4 | import co.touchlab.stately.concurrency.synchronize 5 | 6 | open class ConcurrentMutableCollection internal constructor(rootArg: Synchronizable? = null, private val del: MutableCollection) : 7 | Synchronizable(), 8 | MutableCollection { 9 | 10 | internal val syncTarget: Synchronizable = rootArg ?: this 11 | 12 | override val size: Int 13 | get() = syncTarget.synchronize { del.size } 14 | 15 | override fun contains(element: E): Boolean = syncTarget.synchronize { del.contains(element) } 16 | 17 | override fun containsAll(elements: Collection): Boolean = syncTarget.synchronize { del.containsAll(elements) } 18 | 19 | override fun isEmpty(): Boolean = syncTarget.synchronize { del.isEmpty() } 20 | 21 | override fun add(element: E): Boolean = syncTarget.synchronize { del.add(element) } 22 | 23 | override fun addAll(elements: Collection): Boolean = syncTarget.synchronize { del.addAll(elements) } 24 | 25 | override fun clear() { 26 | syncTarget.synchronize { del.clear() } 27 | } 28 | 29 | override fun iterator(): MutableIterator = 30 | syncTarget.synchronize { ConcurrentMutableIterator(syncTarget, del.iterator()) } 31 | 32 | override fun remove(element: E): Boolean = syncTarget.synchronize { del.remove(element) } 33 | 34 | override fun removeAll(elements: Collection): Boolean = syncTarget.synchronize { del.removeAll(elements) } 35 | 36 | override fun retainAll(elements: Collection): Boolean = syncTarget.synchronize { del.retainAll(elements) } 37 | 38 | fun blockCollection(f: (MutableCollection) -> R): R = syncTarget.synchronize { 39 | val wrapper = MutableCollectionWrapper(del) 40 | val result = f(wrapper) 41 | wrapper._coll = null 42 | result 43 | } 44 | } 45 | 46 | internal open class ConcurrentMutableIterator( 47 | private val root: Synchronizable, 48 | private val del: MutableIterator 49 | ) : 50 | Synchronizable(), 51 | MutableIterator { 52 | override fun hasNext(): Boolean = root.synchronize { del.hasNext() } 53 | 54 | override fun next(): E = root.synchronize { del.next() } 55 | 56 | override fun remove() { 57 | root.synchronize { del.remove() } 58 | } 59 | } 60 | 61 | internal open class MutableCollectionWrapper(internal var _coll: MutableCollection?) : MutableCollection { 62 | 63 | // Reference will fail when block is done. This is to prevent bad things in the block call function 64 | private val coll: MutableCollection 65 | get() = _coll!! 66 | 67 | override fun add(element: E): Boolean = coll.add(element) 68 | override fun addAll(elements: Collection): Boolean = coll.addAll(elements) 69 | override fun clear() { 70 | coll.clear() 71 | } 72 | 73 | override fun iterator(): MutableIterator = coll.iterator() 74 | override fun remove(element: E): Boolean = coll.remove(element) 75 | override fun removeAll(elements: Collection): Boolean = coll.removeAll(elements) 76 | override fun retainAll(elements: Collection): Boolean = coll.retainAll(elements) 77 | override val size: Int 78 | get() = coll.size 79 | 80 | override fun contains(element: E): Boolean = coll.contains(element) 81 | override fun containsAll(elements: Collection): Boolean = coll.containsAll(elements) 82 | override fun isEmpty(): Boolean = coll.isEmpty() 83 | } -------------------------------------------------------------------------------- /stately-concurrent-collections/src/commonMain/kotlin/co/touchlab/stately/collections/ConcurrentMutableList.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.collections 2 | 3 | import co.touchlab.stately.concurrency.Synchronizable 4 | import co.touchlab.stately.concurrency.synchronize 5 | 6 | class ConcurrentMutableList internal constructor(rootArg: Synchronizable?, private val del: MutableList) : 7 | ConcurrentMutableCollection(rootArg, del), MutableList { 8 | constructor() : this(null, mutableListOf()) 9 | 10 | override fun get(index: Int): E = syncTarget.synchronize { del.get(index) } 11 | 12 | override fun indexOf(element: E): Int = syncTarget.synchronize { del.indexOf(element) } 13 | 14 | override fun lastIndexOf(element: E): Int = syncTarget.synchronize { del.lastIndexOf(element) } 15 | 16 | override fun add(index: Int, element: E) { 17 | syncTarget.synchronize { del.add(index, element) } 18 | } 19 | 20 | override fun addAll(index: Int, elements: Collection): Boolean = 21 | syncTarget.synchronize { del.addAll(index, elements) } 22 | 23 | override fun listIterator(): MutableListIterator = 24 | syncTarget.synchronize { ConcurrentMutableListIterator(this, del.listIterator()) } 25 | 26 | override fun listIterator(index: Int): MutableListIterator = 27 | syncTarget.synchronize { ConcurrentMutableListIterator(this, del.listIterator(index)) } 28 | 29 | override fun removeAt(index: Int): E = syncTarget.synchronize { del.removeAt(index) } 30 | 31 | override fun set(index: Int, element: E): E = syncTarget.synchronize { del.set(index, element) } 32 | 33 | override fun subList(fromIndex: Int, toIndex: Int): MutableList = 34 | syncTarget.synchronize { ConcurrentMutableList(this, del.subList(fromIndex, toIndex)) } 35 | 36 | fun block(f: (MutableList) -> R): R = syncTarget.synchronize { 37 | val wrapper = MutableListWrapper(del) 38 | val result = f(wrapper) 39 | wrapper.list = mutableListOf() 40 | result 41 | } 42 | } 43 | 44 | internal class MutableListWrapper(internal var list: MutableList) : MutableCollectionWrapper(list), 45 | MutableList { 46 | override fun get(index: Int): E = list.get(index) 47 | 48 | override fun indexOf(element: E): Int = list.indexOf(element) 49 | 50 | override fun lastIndexOf(element: E): Int = list.lastIndexOf(element) 51 | 52 | override fun add(index: Int, element: E) = list.add(index, element) 53 | 54 | override fun addAll(index: Int, elements: Collection): Boolean = list.addAll(index, elements) 55 | 56 | override fun listIterator(): MutableListIterator = list.listIterator() 57 | 58 | override fun listIterator(index: Int): MutableListIterator = list.listIterator(index) 59 | 60 | override fun removeAt(index: Int): E = list.removeAt(index) 61 | 62 | override fun set(index: Int, element: E): E = list.set(index, element) 63 | 64 | override fun subList(fromIndex: Int, toIndex: Int): MutableList = list.subList(fromIndex, toIndex) 65 | } -------------------------------------------------------------------------------- /stately-concurrent-collections/src/commonMain/kotlin/co/touchlab/stately/collections/ConcurrentMutableMap.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.collections 2 | 3 | import co.touchlab.stately.concurrency.Synchronizable 4 | import co.touchlab.stately.concurrency.synchronize 5 | import kotlin.jvm.JvmName 6 | 7 | class ConcurrentMutableMap internal constructor( 8 | rootArg: Synchronizable? = null, 9 | private val del: MutableMap 10 | ) : Synchronizable(), MutableMap { 11 | 12 | constructor() : this(null, mutableMapOf()) 13 | 14 | private val syncTarget: Synchronizable = rootArg ?: this 15 | 16 | override val size: Int 17 | get() = syncTarget.synchronize { del.size } 18 | override val entries: MutableSet> 19 | get() = syncTarget.synchronize { ConcurrentMutableSet(this, del.entries) } 20 | override val keys: MutableSet 21 | get() = syncTarget.synchronize { ConcurrentMutableSet(this, del.keys) } 22 | override val values: MutableCollection 23 | get() = syncTarget.synchronize { ConcurrentMutableCollection(this, del.values) } 24 | 25 | override fun containsKey(key: K): Boolean = syncTarget.synchronize { del.containsKey(key) } 26 | override fun containsValue(value: V): Boolean = syncTarget.synchronize { del.containsValue(value) } 27 | override fun get(key: K): V? = syncTarget.synchronize { del.get(key) } 28 | override fun isEmpty(): Boolean = syncTarget.synchronize { del.isEmpty() } 29 | override fun clear() { 30 | syncTarget.synchronize { del.clear() } 31 | } 32 | 33 | /** 34 | * If the specified key is not already associated with a value 35 | * attempts to compute its value using the given mapping function and enters it into this map 36 | */ 37 | @JvmName("safeComputeIfAbsent") 38 | fun computeIfAbsent(key: K, defaultValue: (K) -> V): V { 39 | return syncTarget.synchronize { 40 | val value = del[key] 41 | if (value == null) { 42 | val newValue = defaultValue(key) 43 | del[key] = newValue 44 | newValue 45 | } else { 46 | value 47 | } 48 | } 49 | } 50 | 51 | override fun put(key: K, value: V): V? = syncTarget.synchronize { del.put(key, value) } 52 | override fun putAll(from: Map) { 53 | syncTarget.synchronize { del.putAll(from) } 54 | } 55 | 56 | override fun remove(key: K): V? = syncTarget.synchronize { del.remove(key) } 57 | 58 | fun block(f: (MutableMap) -> R): R = syncTarget.synchronize { 59 | val wrapper = MutableMapWrapper(del) 60 | val result = f(wrapper) 61 | wrapper.map = mutableMapOf() 62 | result 63 | } 64 | } 65 | 66 | internal class ConcurrentMutableListIterator( 67 | private val root: Synchronizable, 68 | private val del: MutableListIterator 69 | ) : 70 | ConcurrentMutableIterator(root, del), 71 | MutableListIterator { 72 | override fun hasPrevious(): Boolean = root.synchronize { del.hasPrevious() } 73 | 74 | override fun nextIndex(): Int = root.synchronize { del.nextIndex() } 75 | 76 | override fun previous(): E = root.synchronize { del.previous() } 77 | 78 | override fun previousIndex(): Int = root.synchronize { del.previousIndex() } 79 | 80 | override fun add(element: E) { 81 | root.synchronize { del.add(element) } 82 | } 83 | 84 | override fun set(element: E) { 85 | root.synchronize { del.set(element) } 86 | } 87 | } 88 | 89 | internal class MutableMapWrapper(internal var map: MutableMap) : MutableMap { 90 | override val size: Int 91 | get() = map.size 92 | 93 | override fun containsKey(key: K): Boolean = map.containsKey(key) 94 | 95 | override fun containsValue(value: V): Boolean = map.containsValue(value) 96 | 97 | override fun get(key: K): V? = map.get(key) 98 | 99 | override fun isEmpty(): Boolean = map.isEmpty() 100 | 101 | override val entries: MutableSet> 102 | get() = map.entries 103 | override val keys: MutableSet 104 | get() = map.keys 105 | override val values: MutableCollection 106 | get() = map.values 107 | 108 | override fun clear() { 109 | map.clear() 110 | } 111 | 112 | override fun put(key: K, value: V): V? = map.put(key, value) 113 | 114 | override fun putAll(from: Map) { 115 | map.putAll(from) 116 | } 117 | 118 | override fun remove(key: K): V? = map.remove(key) 119 | } -------------------------------------------------------------------------------- /stately-concurrent-collections/src/commonMain/kotlin/co/touchlab/stately/collections/ConcurrentMutableSet.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.collections 2 | 3 | import co.touchlab.stately.concurrency.Synchronizable 4 | import co.touchlab.stately.concurrency.synchronize 5 | 6 | class ConcurrentMutableSet internal constructor(rootArg: Synchronizable?, private val del: MutableSet) : 7 | ConcurrentMutableCollection(rootArg, del), 8 | MutableSet { 9 | constructor() : this(null, mutableSetOf()) 10 | 11 | fun block(f: (MutableSet) -> R): R = syncTarget.synchronize { 12 | val wrapper = MutableSetWrapper(del) 13 | val result = f(wrapper) 14 | wrapper.set = mutableSetOf() 15 | result 16 | } 17 | } 18 | 19 | internal class MutableSetWrapper(internal var set: MutableSet) : MutableCollectionWrapper(set), MutableSet -------------------------------------------------------------------------------- /stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ConcurrentMutableCollectionTest.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.collections 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | import kotlin.test.assertFails 6 | import kotlin.test.assertTrue 7 | 8 | class ConcurrentMutableCollectionTest { 9 | @Test 10 | @NoJsTest 11 | fun add() { 12 | runBoth(block = { coll, count -> 13 | coll.add(SomeData("count $count")) 14 | }) { 15 | assertEquals(it.size, DEFAULT_RUNS * 2) 16 | } 17 | } 18 | 19 | @Test 20 | @NoJsTest 21 | fun contains() { 22 | runBoth(runs = 1_000, block = { coll, count -> 23 | coll.add(SomeData("count $count")) 24 | }) { coll -> 25 | repeat(2_000) { count -> 26 | coll.contains(SomeData("count $count")) 27 | } 28 | } 29 | } 30 | 31 | @Test 32 | @NoJsTest 33 | fun containsAll() { 34 | val checkList = ConcurrentMutableList() 35 | runBoth(runs = 1_000, block = { coll, count -> 36 | val someData = SomeData("count $count") 37 | coll.add(someData) 38 | checkList.add(someData) 39 | }) { coll -> 40 | coll.containsAll(checkList) 41 | } 42 | } 43 | 44 | @Test 45 | @NoJsTest 46 | fun blockCollection() { 47 | runBoth(runs = 100, block = { coll, count -> 48 | coll.blockCollection { mcoll -> 49 | repeat(20) { innerCount -> 50 | val someData = SomeData("outer $count, inner $innerCount") 51 | mcoll.add(someData) 52 | } 53 | } 54 | }) { coll -> 55 | assertEquals(4000, coll.blockCollection { it.size }) 56 | } 57 | } 58 | 59 | @Test 60 | @NoJsTest 61 | fun blockCollectionLeak() { 62 | runBoth(runs = 100, block = { coll, count -> 63 | coll.blockCollection { mcoll -> 64 | repeat(20) { innerCount -> 65 | val someData = SomeData("outer $count, inner $innerCount") 66 | mcoll.add(someData) 67 | } 68 | } 69 | }) { coll -> 70 | var holdLeak: MutableCollection? = null 71 | coll.blockCollection { holdLeak = it } 72 | assertFails { holdLeak!!.size } 73 | } 74 | } 75 | 76 | @Test 77 | @NoJsTest 78 | fun remove() { 79 | val checkList = ConcurrentMutableSet() 80 | runBoth(runs = 1_000, block = { coll, count -> 81 | val someData = SomeData("count $count") 82 | coll.add(someData) 83 | if (count % 10 == 0) { 84 | checkList.add(someData) 85 | } 86 | }) { coll -> 87 | checkList.forEach { assertTrue(coll.remove(it)) } 88 | assertEquals(checkList.size, 200) 89 | assertEquals(coll.size, 1800) 90 | } 91 | } 92 | 93 | @Test 94 | @NoJsTest 95 | fun removeAll() { 96 | val checkList = ConcurrentMutableSet() 97 | runBoth(runs = 1_000, block = { coll, count -> 98 | val someData = SomeData("count $count") 99 | coll.add(someData) 100 | if (count % 10 == 0) { 101 | checkList.add(someData) 102 | } 103 | }) { coll -> 104 | assertTrue(coll.removeAll(checkList)) 105 | assertEquals(checkList.size, 200) 106 | assertEquals(coll.size, 1800) 107 | } 108 | } 109 | 110 | @Test 111 | @NoJsTest 112 | fun retainAll() { 113 | val checkList = ConcurrentMutableSet() 114 | runBoth(runs = 1_000, block = { coll, count -> 115 | val someData = SomeData("count $count") 116 | coll.add(someData) 117 | if (count % 10 == 0) { 118 | checkList.add(someData) 119 | } 120 | }) { coll -> 121 | assertTrue(coll.retainAll(checkList)) 122 | assertEquals(checkList.size, 200) 123 | assertEquals(coll.size, 200) 124 | } 125 | } 126 | } 127 | 128 | fun runBoth( 129 | runs: Int = DEFAULT_RUNS, 130 | block: (ConcurrentMutableCollection, Int) -> Unit, 131 | verify: (ConcurrentMutableCollection) -> Unit 132 | ) { 133 | val set = ConcurrentMutableSet() 134 | runAlot(runs) { 135 | block(set, it) 136 | } 137 | verify(set) 138 | val list = ConcurrentMutableList() 139 | runAlot(runs) { 140 | block(list, it) 141 | } 142 | verify(list) 143 | } -------------------------------------------------------------------------------- /stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ConcurrentMutableListTest.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.collections 2 | 3 | import co.touchlab.stately.concurrency.ThreadRef 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.async 6 | import kotlinx.coroutines.test.runTest 7 | import kotlin.test.Test 8 | import kotlin.test.assertEquals 9 | 10 | class ConcurrentMutableListTest { 11 | @Test 12 | @NoJsTest 13 | fun tryConcurrent() { 14 | val threadRef = ThreadRef() 15 | val list = ConcurrentMutableList() 16 | 17 | runAlot { 18 | list.add(SomeData("arst $it")) 19 | if (it % (DEFAULT_RUNS / 10) == 0) 20 | println("count $it thread: ${threadRef.same()}") 21 | } 22 | 23 | assertEquals(list.size, DEFAULT_RUNS * 2) 24 | } 25 | 26 | @Test 27 | @NoJsTest 28 | fun tryBlock() { 29 | val list = ConcurrentMutableList() 30 | 31 | runAlot(100) { outerCount -> 32 | list.block { 33 | repeat(1000) { innerCount -> 34 | list.add(SomeData("arst ${innerCount}")) 35 | } 36 | } 37 | } 38 | 39 | assertEquals(list.size, DEFAULT_RUNS * 2) 40 | } 41 | } 42 | 43 | fun runAlot(runs: Int = DEFAULT_RUNS, block: (Int) -> Unit) = runTest { 44 | val job = async(backgroundDispatcher) { 45 | repeat(runs) { 46 | block(it + runs) 47 | } 48 | } 49 | 50 | repeat(runs) { 51 | block(it) 52 | } 53 | 54 | job.await() 55 | } 56 | 57 | data class SomeData(val s: String) 58 | 59 | expect val backgroundDispatcher: CoroutineDispatcher 60 | 61 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) 62 | expect annotation class NoJsTest() -------------------------------------------------------------------------------- /stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/ConcurrentMutableMapTest.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.collections 2 | 3 | import co.touchlab.stately.concurrency.AtomicInt 4 | import co.touchlab.testhelp.concurrency.sleep 5 | import kotlinx.coroutines.delay 6 | import kotlin.test.Test 7 | import kotlin.test.assertEquals 8 | import kotlin.test.assertTrue 9 | 10 | class ConcurrentMutableMapTest { 11 | @Test 12 | @NoJsTest 13 | fun put() { 14 | val map = ConcurrentMutableMap() 15 | 16 | runAlot { run -> 17 | map.put("key $run", SomeData("value $run")) 18 | } 19 | 20 | assertEquals(map.size, 200_000) 21 | } 22 | 23 | @Test 24 | @NoJsTest 25 | fun contains() { 26 | val map = ConcurrentMutableMap() 27 | 28 | runAlot(runs = 10_000) { run -> 29 | map.put("key $run", SomeData("value $run")) 30 | } 31 | 32 | repeat(1000) { i -> 33 | val key = "key $i" 34 | assertTrue(map.containsKey(key), "Key not found '${key}'") 35 | val valueString = "value $i" 36 | assertTrue(map.containsValue(SomeData(valueString)), "Value not found '${valueString}'") 37 | } 38 | } 39 | 40 | @Test 41 | @NoJsTest 42 | fun remove() { 43 | val map = ConcurrentMutableMap() 44 | 45 | runAlot { run -> 46 | map.put("key $run", SomeData("value $run")) 47 | } 48 | 49 | assertEquals(map.size, DEFAULT_RUNS * 2) 50 | 51 | runAlot(runs = DEFAULT_RUNS / 10) { run -> 52 | map.remove("key ${run * 10}") 53 | } 54 | 55 | assertEquals(map.size, (DEFAULT_RUNS * 2) - ((DEFAULT_RUNS / 10) * 2)) 56 | } 57 | 58 | @Test 59 | @NoJsTest 60 | fun block() { 61 | val map = ConcurrentMutableMap() 62 | 63 | runAlot(100) { run -> 64 | map.block { map -> 65 | repeat(1000) { innerRun -> 66 | val item = (run * 1000) + innerRun 67 | map.put("key $item", SomeData("value $item")) 68 | } 69 | } 70 | } 71 | 72 | assertEquals(map.size, DEFAULT_RUNS * 2) 73 | } 74 | 75 | @Test 76 | @NoJsTest 77 | fun computeIfAbsent() { 78 | val map = ConcurrentMutableMap() 79 | val count = AtomicInt(0) 80 | 81 | runAlot(1) { run -> 82 | map.computeIfAbsent("key") { 83 | sleep(1000) 84 | count.incrementAndGet() 85 | SomeData("value $run") 86 | } 87 | } 88 | 89 | assertEquals(count.get(), 1) 90 | } 91 | } -------------------------------------------------------------------------------- /stately-concurrent-collections/src/commonTest/kotlin/co/touchlab/stately/collections/TestHelpers.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.collections 2 | 3 | val DEFAULT_RUNS = 100_000 -------------------------------------------------------------------------------- /stately-concurrent-collections/src/jsAndWasmJsTest/kotlin/co/touchlab/stately/collections/backgroundDispatcher.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.collections 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlin.test.Ignore 6 | 7 | actual val backgroundDispatcher: CoroutineDispatcher 8 | get() = Dispatchers.Main 9 | 10 | actual typealias NoJsTest = Ignore -------------------------------------------------------------------------------- /stately-concurrent-collections/src/jvmTest/kotlin/co/touchlab/stately/collections/backgroundDispatcher.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.collections 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | 6 | actual val backgroundDispatcher: CoroutineDispatcher 7 | get() = Dispatchers.Default 8 | 9 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) 10 | actual annotation class NoJsTest -------------------------------------------------------------------------------- /stately-concurrent-collections/src/nativeTest/kotlin/co/touchlab/stately/collections/backgroundDispatcher.kt: -------------------------------------------------------------------------------- 1 | package co.touchlab.stately.collections 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | 6 | actual val backgroundDispatcher: CoroutineDispatcher 7 | get() = Dispatchers.Default 8 | 9 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) 10 | actual annotation class NoJsTest -------------------------------------------------------------------------------- /stately-strict/api/stately-strict.api: -------------------------------------------------------------------------------- 1 | public final class co/touchlab/stately/strict/HelpersJVMKt { 2 | public static final fun getStrictMemoryModel ()Z 3 | public static final fun maybeFreeze (Ljava/lang/Object;)Ljava/lang/Object; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /stately-strict/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") 3 | id("com.vanniktech.maven.publish") 4 | id("kmp-setup") 5 | } 6 | 7 | val GROUP: String by project 8 | val VERSION_NAME: String by project 9 | 10 | group = GROUP 11 | version = VERSION_NAME 12 | 13 | kotlin { 14 | sourceSets { 15 | commonTest.dependencies { 16 | implementation(kotlin("test")) 17 | implementation(libs.testHelp) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /stately-strict/src/commonMain/kotlin/co/touchlab/stately/strict/Helpers.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 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.strict 18 | 19 | expect fun T.maybeFreeze(): T 20 | 21 | expect val strictMemoryModel: Boolean -------------------------------------------------------------------------------- /stately-strict/src/jsAndWasmJsMain/kotlin/co/touchlab/stately/strict/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.strict 18 | 19 | actual fun T.maybeFreeze(): T = this 20 | actual val strictMemoryModel: Boolean = false -------------------------------------------------------------------------------- /stately-strict/src/jvmMain/kotlin/co/touchlab/stately/strict/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.strict 18 | 19 | actual fun T.maybeFreeze(): T = this 20 | actual val strictMemoryModel: Boolean = false 21 | -------------------------------------------------------------------------------- /stately-strict/src/nativeMain/kotlin/co/touchlab/stately/strict/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.strict 20 | 21 | import kotlin.experimental.ExperimentalNativeApi 22 | import kotlin.native.concurrent.freeze 23 | 24 | @OptIn(ExperimentalNativeApi::class) 25 | actual fun T.maybeFreeze(): T = if (Platform.memoryModel == MemoryModel.STRICT) { 26 | this.freeze() 27 | } else { 28 | this 29 | } 30 | 31 | @OptIn(ExperimentalNativeApi::class) 32 | actual val strictMemoryModel: Boolean 33 | get() = Platform.memoryModel == MemoryModel.STRICT -------------------------------------------------------------------------------- /stately-strict/src/nativeTest/kotlin/co/touchlab/stately/strict/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.strict 18 | 19 | import co.touchlab.testhelp.isFrozen 20 | import kotlin.experimental.ExperimentalNativeApi 21 | import kotlin.test.Test 22 | import kotlin.test.assertEquals 23 | 24 | class HelpersTest { 25 | @OptIn(ExperimentalNativeApi::class) 26 | @Test 27 | fun maybeFreezeTest(){ 28 | val h = Heyo("hello").maybeFreeze() 29 | assertEquals(h.isFrozen, Platform.memoryModel == MemoryModel.STRICT && Platform.isFreezingEnabled) 30 | } 31 | 32 | data class Heyo(val s:String) 33 | } 34 | --------------------------------------------------------------------------------