├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── io │ │ │ │ └── objectbox │ │ │ │ └── dartbench │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── bin └── objectbox.dart ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── executor.dart ├── hive_executor.dart ├── isar_model.dart ├── isar_model.g.dart ├── isar_sync_executor.dart ├── main.dart ├── model.dart ├── model.g.dart ├── objectbox-model.json ├── objectbox.g.dart ├── obx_executor.dart ├── sqf_executor.dart └── time_tracker.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app_icon_1024.png │ │ ├── app_icon_128.png │ │ ├── app_icon_16.png │ │ ├── app_icon_256.png │ │ ├── app_icon_32.png │ │ ├── app_icon_512.png │ │ └── app_icon_64.png │ ├── Base.lproj │ └── MainMenu.xib │ ├── Configs │ ├── AppInfo.xcconfig │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── run_loop.cpp ├── run_loop.h ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | ../lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # ObjectBox DB files 44 | **.mdb 45 | 46 | android/app/google-services.json -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1aafb3a8b9b0c36241c5f5b34ee914770f015818 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ObjectBox Flutter Database Performance Benchmarks 2 | 3 | ## Setup 4 | 5 | As usual run `flutter pub get`. 6 | 7 | ## Running 8 | 9 | Make sure to run the app in release mode to avoid any performance penalties from debug mode. 10 | (Note: release mode is not supported on the iOS simulator, a real device is required.) 11 | 12 | To run in release mode with Android Studio connect a device or start an emulator, then 13 | Run > Flutter Run 'main.dart' in Release Mode. 14 | 15 | Or run `flutter run --release`. 16 | 17 | To further improve performance, make sure to disconnect dev tools. E.g. stop the app on the device 18 | and launch it again. 19 | 20 | ## Implementation notes 21 | 22 | ### Hive 23 | 24 | - No test with index as Hive does not support explicit indexes. 25 | 26 | ### Isar 27 | 28 | - Instead of read all does use query with no conditions and returns all results. 29 | - Query tests not available (queryWithLinks not implemented). 30 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/tools/linter-rules. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/tools/analysis 29 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "io.objectbox.dartbench" 27 | compileSdk flutter.compileSdkVersion 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | applicationId "io.objectbox.dartbench" 45 | // You can update the following values to match your application needs. 46 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 47 | minSdkVersion flutter.minSdkVersion 48 | targetSdkVersion flutter.targetSdkVersion 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | flutter { 63 | source '../..' 64 | } 65 | 66 | dependencies {} 67 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/io/objectbox/dartbench/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.objectbox.dartbench 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | } 19 | 20 | plugins { 21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 22 | id "com.android.application" version "7.3.0" apply false 23 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false 24 | } 25 | 26 | include ":app" 27 | -------------------------------------------------------------------------------- /bin/objectbox.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_print 2 | 3 | import 'dart:io'; 4 | 5 | import 'package:args/args.dart'; 6 | import 'package:benchapp/obx_executor.dart'; 7 | import 'package:benchapp/time_tracker.dart'; 8 | 9 | void main(List arguments) async { 10 | exitCode = 0; // presume success 11 | 12 | const argDb = 'db'; 13 | const argCount = 'count'; 14 | const argRuns = 'runs'; 15 | 16 | final parser = ArgParser() 17 | ..addOption(argDb, defaultsTo: 'benchmark-db', help: 'database directory') 18 | ..addOption(argCount, defaultsTo: '10000', help: 'number of objects') 19 | ..addOption(argRuns, 20 | defaultsTo: '30', help: 'number of times the tests should be executed'); 21 | 22 | final args = parser.parse(arguments); 23 | final dbDir = Directory(args[argDb]); 24 | final count = int.parse(args[argCount]); 25 | final runs = int.parse(args[argRuns]); 26 | 27 | print('running $runs times with $count objects in $dbDir'); 28 | 29 | if (dbDir.existsSync()) { 30 | print('deleting existing DB directory $dbDir'); 31 | dbDir.deleteSync(recursive: true); 32 | } 33 | 34 | final tracker = TimeTracker((Iterable columns) => print(columns)); 35 | final bench = ExecutorPlain(dbDir, tracker); 36 | 37 | final inserts = bench.prepareData(count); 38 | 39 | for (var i = 0; i < runs; i++) { 40 | bench.insertMany(inserts); 41 | final ids = inserts.map((e) => e.id).toList(growable: false); 42 | final items = await bench.readAll(ids); 43 | bench.updateMany(items); 44 | bench.removeMany(ids); 45 | 46 | print('${i + 1}/$runs finished'); 47 | } 48 | 49 | bench.close(); 50 | tracker.printTimes(functions: [ 51 | 'insertMany', 52 | 'readAll', 53 | 'updateMany', 54 | 'removeMany', 55 | ]); 56 | } 57 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - FMDB (2.7.5): 4 | - FMDB/standard (= 2.7.5) 5 | - FMDB/standard (2.7.5) 6 | - isar_flutter_libs (1.0.0): 7 | - Flutter 8 | - ObjectBox (1.8.1) 9 | - objectbox_flutter_libs (0.0.1): 10 | - Flutter 11 | - ObjectBox (= 1.8.1) 12 | - path_provider_foundation (0.0.1): 13 | - Flutter 14 | - FlutterMacOS 15 | - sqflite (0.0.2): 16 | - Flutter 17 | - FMDB (>= 2.7.5) 18 | 19 | DEPENDENCIES: 20 | - Flutter (from `Flutter`) 21 | - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) 22 | - objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`) 23 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) 24 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 25 | 26 | SPEC REPOS: 27 | trunk: 28 | - FMDB 29 | - ObjectBox 30 | 31 | EXTERNAL SOURCES: 32 | Flutter: 33 | :path: Flutter 34 | isar_flutter_libs: 35 | :path: ".symlinks/plugins/isar_flutter_libs/ios" 36 | objectbox_flutter_libs: 37 | :path: ".symlinks/plugins/objectbox_flutter_libs/ios" 38 | path_provider_foundation: 39 | :path: ".symlinks/plugins/path_provider_foundation/ios" 40 | sqflite: 41 | :path: ".symlinks/plugins/sqflite/ios" 42 | 43 | SPEC CHECKSUMS: 44 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 45 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 46 | isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 47 | ObjectBox: a7900d5335218cd437cbc080b7ccc38a5211f7b4 48 | objectbox_flutter_libs: 61d74196d924fbc773da5f5757d1e9fab7b3cc78 49 | path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 50 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 51 | 52 | PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 53 | 54 | COCOAPODS: 1.12.0 55 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | benchapp 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/executor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'model.dart'; 4 | import 'time_tracker.dart'; 5 | 6 | class ConfigQueryWithLinks { 7 | final String sourceStringEquals; 8 | final int sourceIntEquals; 9 | final String targetStringEquals; 10 | 11 | ConfigQueryWithLinks( 12 | this.sourceStringEquals, this.sourceIntEquals, this.targetStringEquals); 13 | } 14 | 15 | abstract class ExecutorBase { 16 | static const caseSensitive = true; 17 | 18 | final TimeTracker tracker; 19 | 20 | ExecutorBase(this.tracker); 21 | 22 | Future close(); 23 | 24 | /// Create an entity with the given values. 25 | T createEntity(String tString, int tInt, int tLong, double tDouble); 26 | 27 | List prepareData(int count) => List.generate( 28 | count, (i) => createEntity('Entity #$i', i, i, i.toDouble()), 29 | growable: false); 30 | 31 | void changeValues(List items) { 32 | for (var item in items) { 33 | item.tLong *= 2; 34 | } 35 | } 36 | 37 | List allNotNull(List items) => 38 | items.map((e) => e!).toList(growable: false); 39 | 40 | /// If available should use a read all function, 41 | /// otherwise use the ID list to look up all items. 42 | Future> readAll(List optionalIds) => throw UnimplementedError(); 43 | 44 | Future insertMany(List items); 45 | 46 | Future updateMany(List items); 47 | 48 | Future> queryById(List ids, [String? benchmarkQualifier]); 49 | 50 | Future removeMany(List ids); 51 | 52 | Future> queryStringEquals(List values) => 53 | throw UnimplementedError(); 54 | 55 | /// Verifies that the executor works as expected (returns proper results). 56 | Future test({required int count, String? qString}) => 57 | Future.sync(() async { 58 | checkCount(String message, Iterable list, int count) => 59 | RangeError.checkValueInInterval(list.length, count, count, message); 60 | 61 | final inserts = prepareData(count); 62 | await insertMany(inserts); 63 | 64 | final ids = inserts.map((e) => e.id).toList(growable: false); 65 | checkCount('insertMany assigns ids', ids.toSet(), count); 66 | 67 | final items = allNotNull(await queryById(ids)); 68 | checkCount('queryById', items, count); 69 | 70 | final itemsAll = allNotNull(await readAll(ids)); 71 | checkCount('readAll', itemsAll, count); 72 | 73 | changeValues(items); 74 | await updateMany(items); 75 | 76 | if (qString != null) { 77 | checkCount('query string', await queryStringEquals([qString]), 1); 78 | } 79 | 80 | checkCount('count before remove', 81 | (await queryById(ids)).where((e) => e != null), count); 82 | await removeMany(ids); 83 | checkCount('count after remove', 84 | (await queryById(ids)).where((e) => e != null), 0); 85 | }); 86 | 87 | Future createRelBenchmark() => throw UnimplementedError(); 88 | } 89 | 90 | /// Benchmark executor base class for relations tests 91 | abstract class ExecutorBaseRel { 92 | final TimeTracker tracker; 93 | 94 | ExecutorBaseRel(this.tracker); 95 | 96 | Future close(); 97 | 98 | bool get indexed => T == RelSourceEntityIndexed; 99 | 100 | /// About 100 sources have the same group so group query condition 101 | /// returns about 100 results regardless of source object count. 102 | static int distinctSourceStrings(int sourceObjectCount) => 103 | max(1, sourceObjectCount ~/ 100); 104 | 105 | /// Creates repeating group + target combination blocks 106 | /// where for the first distinctSourceStrings sources 107 | /// the group number and target number match. 108 | /// So overall there exist up to count / targets.length blocks 109 | /// == sources where group and target number match. 110 | /// The int is 0 or 1 based on the source index being even or odd. 111 | /// So the block length = targets.length should be odd to make sure 112 | /// every other block has a different int + group + target combination. 113 | List prepareDataSources(int count, List targets) => 114 | List.generate(count, (i) { 115 | // Start source group number at 0 again if multiple 116 | // of target.length (= next block) reached to ensure 117 | // each block starts with matching group and target numbers. 118 | final groupNumber = i % targets.length % distinctSourceStrings(count); 119 | final string = 'Source group #$groupNumber'; 120 | final target = targets[i % targets.length]; 121 | // Every other source has int 0 or 1 122 | return indexed 123 | ? RelSourceEntityIndexed.forInsert(string, i % 2, target) as T 124 | : RelSourceEntityPlain.forInsert(string, i % 2, target) as T; 125 | }, growable: false); 126 | 127 | List prepareDataTargets(int count) => 128 | List.generate(count, (i) => RelTargetEntity(0, 'Target #$i'), 129 | growable: false); 130 | 131 | Future insertData(int relSourceCount, int relTargetCount); 132 | 133 | Future> queryWithLinks(List args) => 134 | throw UnimplementedError(); 135 | } 136 | -------------------------------------------------------------------------------- /lib/hive_executor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:hive/hive.dart'; 4 | 5 | import 'executor.dart'; 6 | import 'model.dart'; 7 | import 'time_tracker.dart'; 8 | 9 | /// Note: There are no explicit indixes in Hive. 10 | class Executor extends ExecutorBase { 11 | final Box _box; 12 | final String _dir; 13 | 14 | Executor._(this._dir, this._box, TimeTracker tracker) : super(tracker); 15 | 16 | static Future create(Directory dbDir, TimeTracker tracker) async { 17 | if (!Hive.isAdapterRegistered(TestEntityPlainAdapter().typeId)) { 18 | Hive.registerAdapter(TestEntityPlainAdapter()); 19 | } 20 | return Executor._( 21 | dbDir.path, 22 | await Hive.openBox((TestEntityPlain).toString(), path: dbDir.path), 23 | tracker); 24 | } 25 | 26 | @override 27 | Future close() async => await _box.close(); 28 | 29 | @override 30 | TestEntityPlain createEntity( 31 | String tString, int tInt, int tLong, double tDouble) { 32 | return TestEntityPlain(0, tString, tInt, tLong, tDouble); 33 | } 34 | 35 | @override 36 | Future insertMany(List items) async => 37 | tracker.trackAsync('insertMany', () async { 38 | int id = 1; 39 | final itemsById = {}; 40 | for (var o in items) { 41 | if (o.id == 0) o.id = id++; 42 | itemsById[o.id] = o; 43 | } 44 | return await _box.putAll(itemsById); 45 | }); 46 | 47 | @override 48 | Future updateMany(List items) async => 49 | tracker.trackAsync('updateMany', 50 | () async => await _box.putAll({for (var o in items) o.id: o})); 51 | 52 | @override 53 | Future> readAll(List optionalIds) => 54 | Future.value(tracker.track('readAll', () => _box.values.toList())); 55 | 56 | @override 57 | Future> queryById(List ids, 58 | [String? benchmarkQualifier]) => 59 | Future.value(tracker.track('queryById${benchmarkQualifier ?? ''}', 60 | () => ids.map(_box.get).toList())); 61 | 62 | @override 63 | Future removeMany(List ids) async => 64 | tracker.trackAsync('removeMany', () async { 65 | await _box.deleteAll(ids); 66 | await _box.compact(); 67 | }); 68 | 69 | // Hive doesn't have queries -> let's emulate 70 | @override 71 | Future> queryStringEquals(List values) { 72 | if (!ExecutorBase.caseSensitive) { 73 | values = values.map((e) => e.toLowerCase()).toList(growable: false); 74 | } 75 | return Future.value(tracker.track('queryStringEquals', () { 76 | late List result; 77 | final length = values.length; 78 | for (var i = 0; i < length; i++) { 79 | result = _box.values 80 | .where((TestEntityPlain object) => ExecutorBase.caseSensitive 81 | ? object.tString == values[i] 82 | : object.tString.toLowerCase() == values[i]) 83 | .toList(); 84 | } 85 | return result; 86 | })); 87 | } 88 | 89 | @override 90 | Future createRelBenchmark() => 91 | ExecutorRel.create(tracker, _dir); 92 | } 93 | 94 | class ExecutorRel extends ExecutorBaseRel { 95 | final Box _box; 96 | final Box _boxTarget; 97 | 98 | static Future> create( 99 | TimeTracker tracker, String? path) async { 100 | if (!Hive.isAdapterRegistered(RelSourceEntityPlainAdapter().typeId)) { 101 | Hive.registerAdapter(RelSourceEntityPlainAdapter()); 102 | } 103 | if (!Hive.isAdapterRegistered(RelTargetEntityAdapter().typeId)) { 104 | Hive.registerAdapter(RelTargetEntityAdapter()); 105 | } 106 | return ExecutorRel._(tracker, await Hive.openBox(T.toString(), path: path), 107 | await Hive.openBox('RelTargetEntity', path: path)); 108 | } 109 | 110 | ExecutorRel._(super.tracker, this._box, this._boxTarget); 111 | 112 | @override 113 | Future close() async { 114 | await _box.close(); 115 | await _boxTarget.close(); 116 | } 117 | 118 | @override 119 | Future insertData(int relSourceCount, int relTargetCount) async { 120 | final targets = prepareDataTargets(relTargetCount); 121 | await _boxTarget.putAll(_itemsById(targets)); 122 | assert(targets.first.id != 0); 123 | final sources = prepareDataSources(relSourceCount, targets); 124 | await _box.putAll(_itemsById(sources)); 125 | assert(_box.length == relSourceCount); 126 | assert(_boxTarget.length == relTargetCount); 127 | } 128 | 129 | @override 130 | Future> queryWithLinks(List args) { 131 | if (!ExecutorBase.caseSensitive) { 132 | for (var config in args) { 133 | config.sourceStringEquals.toLowerCase(); 134 | config.targetStringEquals.toLowerCase(); 135 | } 136 | } 137 | 138 | return Future.value(tracker.track('queryWithLinks', () { 139 | late List result; 140 | final length = args.length; 141 | for (var i = 0; i < length; i++) { 142 | final matchingTargets = _boxTarget.values 143 | .where((RelTargetEntity o) => 144 | (ExecutorBase.caseSensitive ? o.name : o.name.toLowerCase()) == 145 | args[i].targetStringEquals) 146 | .map((e) => e.id) 147 | .toSet(); 148 | assert(matchingTargets.isNotEmpty); 149 | result = _box.values 150 | .where((T o) => 151 | o.tLong == args[i].sourceIntEquals && 152 | matchingTargets 153 | .contains((o as RelSourceEntityPlain).relTargetId) && 154 | (ExecutorBase.caseSensitive 155 | ? o.tString 156 | : o.tString.toLowerCase()) == 157 | args[i].sourceStringEquals) 158 | .toList(); 159 | } 160 | return result; 161 | })); 162 | } 163 | } 164 | 165 | Map _itemsById(List list) { 166 | final result = {}; 167 | var id = 1; 168 | for (var o in list) { 169 | if (o.id == 0) o.id = id++; 170 | result[o.id] = o as EntityT; 171 | } 172 | return result; 173 | } 174 | -------------------------------------------------------------------------------- /lib/isar_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:benchapp/model.dart'; 2 | import 'package:isar/isar.dart'; 3 | 4 | part 'isar_model.g.dart'; 5 | 6 | @collection 7 | class IsarTestEntityPlain implements TestEntity { 8 | @override 9 | Id id; 10 | 11 | @override 12 | String tString; 13 | 14 | @override 15 | int tInt; // 32-bit 16 | 17 | @override 18 | int tLong; // 64-bit 19 | 20 | @override 21 | double tDouble; 22 | 23 | IsarTestEntityPlain( 24 | this.id, this.tString, this.tInt, this.tLong, this.tDouble); 25 | } 26 | 27 | // A separate entity for queried data so that indexes don't change CRUD results. 28 | @Collection() 29 | class IsarTestEntityIndexed implements TestEntity { 30 | @override 31 | Id id; 32 | 33 | @override 34 | @Index(type: IndexType.value) 35 | String tString; 36 | 37 | @override 38 | @Index() 39 | int tInt; // 32-bit 40 | 41 | @override 42 | int tLong; // 64-bit 43 | 44 | @override 45 | double tDouble; 46 | 47 | IsarTestEntityIndexed( 48 | this.id, this.tString, this.tInt, this.tLong, this.tDouble); 49 | } 50 | 51 | @collection 52 | class IsarRelSourceEntityPlain implements RelSourceEntity { 53 | @override 54 | Id id; 55 | 56 | @override 57 | final String tString; 58 | 59 | @override 60 | final int tLong; // 64-bit 61 | 62 | @override 63 | @Ignore() 64 | final int relTargetId; 65 | 66 | final isarRelTarget = IsarLink(); 67 | 68 | // Note: constructor arg types must match with fromMap used by sqflite. 69 | IsarRelSourceEntityPlain(this.id, this.tString, this.tLong, 70 | [this.relTargetId = 0]); 71 | 72 | IsarRelSourceEntityPlain.forInsert( 73 | this.tString, this.tLong, IsarRelTargetEntity? relTarget) 74 | : id = 0, 75 | relTargetId = relTarget?.id ?? 0 { 76 | isarRelTarget.value = relTarget; 77 | } 78 | } 79 | 80 | @collection 81 | class IsarRelSourceEntityIndexed implements RelSourceEntity { 82 | @override 83 | Id id; 84 | 85 | @override 86 | @Index(type: IndexType.value) 87 | final String tString; 88 | 89 | @override 90 | final int tLong; // 64-bit 91 | 92 | @override 93 | @Ignore() 94 | final int relTargetId; 95 | 96 | final isarRelTarget = IsarLink(); 97 | 98 | // Note: constructor arg types must match with fromMap used by sqflite. 99 | IsarRelSourceEntityIndexed(this.id, this.tString, this.tLong, 100 | [this.relTargetId = 0]); 101 | 102 | IsarRelSourceEntityIndexed.forInsert( 103 | this.tString, this.tLong, IsarRelTargetEntity? relTarget) 104 | : id = 0, 105 | relTargetId = relTarget?.id ?? 0 { 106 | isarRelTarget.value = relTarget; 107 | } 108 | } 109 | 110 | @collection 111 | class IsarRelTargetEntity extends EntityWithSettableId { 112 | @override 113 | Id id; 114 | 115 | @Index(type: IndexType.value) 116 | String name; 117 | 118 | IsarRelTargetEntity(this.id, this.name); 119 | } 120 | -------------------------------------------------------------------------------- /lib/isar_sync_executor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:benchapp/isar_model.dart'; 4 | import 'package:isar/isar.dart'; 5 | 6 | import 'executor.dart'; 7 | import 'model.dart'; 8 | import 'time_tracker.dart'; 9 | 10 | abstract class Executor extends ExecutorBase { 11 | final Isar _store; 12 | final IsarCollection _box; 13 | 14 | Executor._(this._store, TimeTracker tracker) 15 | : _box = _store.collection(), 16 | super(tracker); 17 | 18 | @override 19 | Future close() async => await _store.close(); 20 | 21 | @override 22 | Future insertMany(List items) => Future.value( 23 | tracker.track( 24 | 'insertMany', 25 | () { 26 | _assignIds(items); 27 | return _store.writeTxnSync( 28 | () => _box.putAllSync(items), 29 | ); 30 | }, 31 | ), 32 | ); 33 | 34 | @override 35 | Future updateMany(List items) => Future.value(tracker.track( 36 | 'updateMany', () => _store.writeTxnSync(() => _box.putAllSync(items)))); 37 | 38 | // Note: get all is not supported in isar (v2.5.0), instead 39 | // use query with no conditions as suggested in Quickstart. 40 | @override 41 | Future> readAll(List optionalIds) => 42 | Future.value(tracker.track('readAll', () => _box.where().findAllSync())); 43 | 44 | @override 45 | Future> queryById(List ids, [String? benchmarkQualifier]) => 46 | Future.value(tracker.track( 47 | 'queryById${benchmarkQualifier ?? ''}', () => _box.getAllSync(ids))); 48 | 49 | @override 50 | Future removeMany(List ids) => Future.value( 51 | tracker.track('removeMany', 52 | () => _store.writeTxnSync(() => _box.deleteAllSync(ids))), 53 | ); 54 | } 55 | 56 | /// Using an entity without indexes 57 | class ExecutorPlain extends Executor { 58 | ExecutorPlain._(super.store, super.tracker) : super._(); 59 | 60 | static Future> create( 61 | Directory dbDir, TimeTracker tracker) async { 62 | final isar = await Isar.open( 63 | [ 64 | IsarTestEntityPlainSchema, 65 | IsarRelSourceEntityPlainSchema, 66 | IsarRelTargetEntitySchema 67 | ], 68 | directory: dbDir.path, 69 | ); 70 | return ExecutorPlain._(isar, tracker); 71 | } 72 | 73 | @override 74 | IsarTestEntityPlain createEntity( 75 | String tString, int tInt, int tLong, double tDouble) { 76 | return IsarTestEntityPlain(0, tString, tInt, tLong, tDouble); 77 | } 78 | 79 | @override 80 | Future> queryStringEquals( 81 | List values) async => 82 | Future.value(tracker.track('queryStringEquals', () { 83 | late List result; 84 | final length = values.length; 85 | for (var i = 0; i < length; i++) { 86 | result = (_box) 87 | .where() 88 | .filter() 89 | .tStringEqualTo(values[i], 90 | caseSensitive: ExecutorBase.caseSensitive) 91 | .findAllSync(); 92 | } 93 | return result; 94 | })); 95 | 96 | @override 97 | Future createRelBenchmark() => 98 | Future.value(ExecutorRel._(tracker, _store)); 99 | } 100 | 101 | /// Using an entity with indexes 102 | class ExecutorIndexed extends Executor { 103 | ExecutorIndexed._(super.store, super.tracker) : super._(); 104 | 105 | static Future> create( 106 | Directory dbDir, TimeTracker tracker) async { 107 | final isar = await Isar.open( 108 | [ 109 | IsarTestEntityIndexedSchema, 110 | IsarRelSourceEntityIndexedSchema, 111 | IsarRelTargetEntitySchema 112 | ], 113 | directory: dbDir.path, 114 | ); 115 | return ExecutorIndexed._(isar, tracker); 116 | } 117 | 118 | @override 119 | IsarTestEntityIndexed createEntity( 120 | String tString, int tInt, int tLong, double tDouble) { 121 | return IsarTestEntityIndexed(0, tString, tInt, tLong, tDouble); 122 | } 123 | 124 | @override 125 | Future> queryStringEquals( 126 | List values) async => 127 | Future.value(tracker.track('queryStringEquals', () { 128 | // Indexed queries must be case insensitive, this prevents comparison. 129 | // See https://github.com/isar/isar#queries 130 | assert(!ExecutorBase.caseSensitive); 131 | late List result; 132 | final length = values.length; 133 | for (var i = 0; i < length; i++) { 134 | result = (_box).where().tStringEqualTo(values[i]).findAllSync(); 135 | } 136 | return result; 137 | })); 138 | 139 | @override 140 | Future createRelBenchmark() => 141 | Future.value(ExecutorRel._(tracker, _store)); 142 | } 143 | 144 | class ExecutorRel extends ExecutorBaseRel { 145 | final Isar _store; 146 | final IsarCollection _box; 147 | final IsarCollection _boxTarget; 148 | 149 | ExecutorRel._(super.tracker, this._store) 150 | : _box = _store.collection(), 151 | _boxTarget = _store.isarRelTargetEntitys; 152 | 153 | @override 154 | Future close() async => Future.value(); 155 | 156 | @override 157 | Future insertData(int relSourceCount, int relTargetCount) async { 158 | final targets = prepareDataTargetsIsar(relTargetCount); 159 | _assignIds(targets); 160 | _store.writeTxnSync(() => _boxTarget.putAllSync(targets)); 161 | 162 | final sources = prepareDataSourcesIsar(relSourceCount, targets); 163 | _assignIds(sources); 164 | _store.writeTxnSync(() => _box.putAllSync(sources)); 165 | 166 | assert(_box.countSync() == relSourceCount); 167 | assert(_boxTarget.countSync() == relTargetCount); 168 | } 169 | 170 | /// Matches ExecutorBaseRel.prepareDataSources, see for implementation details. 171 | List prepareDataSourcesIsar( 172 | int count, List targets) => 173 | List.generate(count, (i) { 174 | final groupNumber = 175 | i % targets.length % ExecutorBaseRel.distinctSourceStrings(count); 176 | final string = 'Source group #$groupNumber'; 177 | final target = targets[i % targets.length]; 178 | return indexed 179 | ? IsarRelSourceEntityIndexed.forInsert(string, i % 2, target) as T 180 | : IsarRelSourceEntityPlain.forInsert(string, i % 2, target) as T; 181 | }, growable: false); 182 | 183 | List prepareDataTargetsIsar(int count) => 184 | List.generate(count, (i) => IsarRelTargetEntity(0, 'Target #$i'), 185 | growable: false); 186 | 187 | @override 188 | Future> queryWithLinks(List args) { 189 | // TODO implement once the model is properly generated 190 | // see https://isar.dev/queries#links 191 | return Future.error(UnimplementedError('queryWithLinks')); 192 | } 193 | } 194 | 195 | void _assignIds(List list) { 196 | for (var i = 0; i < list.length; i++) { 197 | list[i].id = i + 1; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:path/path.dart' as path; 7 | import 'package:path_provider/path_provider.dart'; 8 | 9 | import 'executor.dart'; 10 | import 'hive_executor.dart' as hive; 11 | import 'isar_sync_executor.dart' as isar_sync; 12 | import 'obx_executor.dart' as obx; 13 | import 'sqf_executor.dart' as sqf; 14 | 15 | // import 'hive_lazy_executor.dart' as hive_lazy; 16 | import 'time_tracker.dart'; 17 | 18 | void main() { 19 | runApp(const MyApp()); 20 | } 21 | 22 | class MyApp extends StatelessWidget { 23 | const MyApp({super.key}); 24 | 25 | // This widget is the root of your application. 26 | @override 27 | Widget build(BuildContext context) { 28 | return MaterialApp( 29 | title: 'DB Benchmark', 30 | theme: ThemeData( 31 | // This is the theme of your application. 32 | // 33 | // Try running your application with "flutter run". You'll see the 34 | // application has a blue toolbar. Then, without quitting the app, try 35 | // changing the primarySwatch below to Colors.green and then invoke 36 | // "hot reload" (press "r" in the console where you ran "flutter run", 37 | // or simply save your changes to "hot reload" in a Flutter IDE). 38 | // Notice that the counter didn't reset back to zero; the application 39 | // is not restarted. 40 | primarySwatch: Colors.blue, 41 | // This makes the visual density adapt to the platform that you run 42 | // the app on. For desktop platforms, the controls will be smaller and 43 | // closer together (more dense) than on mobile platforms. 44 | visualDensity: VisualDensity.adaptivePlatformDensity, 45 | ), 46 | home: const MyHomePage(title: 'DB Benchmark'), 47 | ); 48 | } 49 | } 50 | 51 | class MyHomePage extends StatefulWidget { 52 | const MyHomePage({super.key, required this.title}); 53 | 54 | // This widget is the home page of your application. It is stateful, meaning 55 | // that it has a State object (defined below) that contains fields that affect 56 | // how it looks. 57 | 58 | // This class is the configuration for the state. It holds the values (in this 59 | // case the title) provided by the parent (in this case the App widget) and 60 | // used by the build method of the State. Fields in a Widget subclass are 61 | // always marked "final". 62 | 63 | final String title; 64 | 65 | @override 66 | State createState() => _MyHomePageState(); 67 | } 68 | 69 | enum DbEngine { ObjectBox, sqflite, Hive, IsarSync } 70 | 71 | enum Mode { CRUD, Queries, QueryById } 72 | 73 | enum RunState { idle, running, stopping } 74 | 75 | class _MyHomePageState extends State { 76 | var _db = DbEngine.ObjectBox; 77 | var _mode = Mode.CRUD; 78 | var _indexed = false; 79 | final _objectsController = TextEditingController(text: '10000'); 80 | final _runsController = TextEditingController(text: '10'); 81 | final _operationsController = TextEditingController(text: '1000'); 82 | final _resultsController = TextEditingController(text: '10000'); 83 | late final TimeTracker _tracker = TimeTracker(_print); 84 | 85 | /// The directory where a temporary directory for each benchmark is created in. 86 | final appDir = Completer(); 87 | 88 | var _result = ''; 89 | final _resultRows = []; 90 | RunState _state = RunState.idle; 91 | 92 | void _print(List columns) { 93 | setState(() { 94 | final cols = []; 95 | for (var i = 0; i < columns.length; i++) { 96 | cols.add(Text(columns[i], 97 | softWrap: false, 98 | style: TextStyle( 99 | fontSize: 20, 100 | fontWeight: (_resultRows.isEmpty || columns[i] == 'Count') 101 | ? FontWeight.bold 102 | : FontWeight.normal), 103 | textAlign: i == 0 ? TextAlign.left : TextAlign.right)); 104 | } 105 | _resultRows.add(TableRow(children: cols)); 106 | }); 107 | } 108 | 109 | void configure(DbEngine db, Mode mode, bool indexed) => setState(() { 110 | _db = db; 111 | _mode = mode; 112 | _indexed = indexed; 113 | _result = ''; 114 | _resultRows.clear(); 115 | }); 116 | 117 | @override 118 | void initState() { 119 | super.initState(); 120 | 121 | /// Use getTemporaryDirectory instead of getApplicationDocumentsDirectory to 122 | /// avoid creating and deleting many files in a potentially backed up 123 | /// location on the test device. 124 | getTemporaryDirectory().then((value) { 125 | debugPrint("Using directory: ${value.absolute.path}"); 126 | appDir.complete(value); 127 | }); 128 | } 129 | 130 | Future _createExecutor(Directory dbDir, bool useIndexes) async { 131 | switch (_db) { 132 | case DbEngine.ObjectBox: 133 | if (useIndexes) { 134 | return Future.value(obx.ExecutorIndexed(dbDir, _tracker)); 135 | } else { 136 | return Future.value(obx.ExecutorPlain(dbDir, _tracker)); 137 | } 138 | case DbEngine.sqflite: 139 | final dbFile = Directory(path.join(dbDir.path, 'bench.db')); 140 | if (useIndexes) { 141 | return sqf.ExecutorIndexed.create(dbFile, _tracker); 142 | } else { 143 | return sqf.ExecutorPlain.create(dbFile, _tracker); 144 | } 145 | case DbEngine.Hive: 146 | // No explicit index support for Hive. 147 | return hive.Executor.create(dbDir, _tracker); 148 | case DbEngine.IsarSync: 149 | if (useIndexes) { 150 | return isar_sync.ExecutorIndexed.create(dbDir, _tracker); 151 | } else { 152 | return isar_sync.ExecutorPlain.create(dbDir, _tracker); 153 | } 154 | default: 155 | throw Exception('Unknown executor'); 156 | } 157 | } 158 | 159 | bool get indexed => _indexed && _db != DbEngine.Hive; 160 | 161 | void _stopBenchmark() async => setState(() { 162 | _result = 'Benchmark stopping...'; 163 | _state = RunState.stopping; 164 | }); 165 | 166 | void _runBenchmark() async { 167 | setState(() { 168 | _result = 'Benchmark starting...'; 169 | _resultRows.clear(); 170 | _state = RunState.running; 171 | }); 172 | 173 | final dbDir = (await appDir.future).createTempSync(); 174 | print('Using temporary DB directory $dbDir'); 175 | dbDir.createSync(recursive: true); 176 | 177 | ExecutorBase? executor; 178 | try { 179 | executor = await _createExecutor(dbDir, indexed); 180 | await _runBenchmarkOn(executor); 181 | } finally { 182 | await executor?.close(); 183 | if (dbDir.existsSync()) dbDir.deleteSync(recursive: true); 184 | } 185 | } 186 | 187 | Future printResult(String value) async { 188 | setState(() => _result = value); 189 | await Future.delayed(const Duration(seconds: 0)); // yield to re-render 190 | } 191 | 192 | /// Waits for the given future to complete. Returns true if the benchmark 193 | /// should continue or false if it was stopped by the user in the meantime. 194 | Future awaitOrStop(Future future) async { 195 | await Future.any([ 196 | future, 197 | Future.microtask(() async { 198 | while (_state == RunState.running) { 199 | await Future.delayed(const Duration(milliseconds: 10)); 200 | } 201 | return Future.value(); 202 | }) 203 | ]); 204 | return Future.value(_state == RunState.running); 205 | } 206 | 207 | Future _runBenchmarkOn(ExecutorBase bench) async { 208 | final objectsCount = int.parse(_objectsController.value.text); 209 | final runs = int.parse(_runsController.value.text); 210 | 211 | // Before we start to benchmark: verify the executor works as expected. 212 | // assert() makes this only run in debug mode 213 | assert(await _testBenchmark(bench)); 214 | 215 | _tracker.clear(); 216 | 217 | try { 218 | switch (_mode) { 219 | case Mode.CRUD: 220 | for (var i = 0; i < runs && _state == RunState.running; i++) { 221 | final inserts = bench.prepareData(objectsCount); 222 | if (!await awaitOrStop(bench.insertMany(inserts))) { 223 | break; 224 | } 225 | final ids = inserts.map((e) => e.id).toList(growable: false); 226 | final itemsOptional = await bench.readAll(ids); 227 | final items = bench.allNotNull(itemsOptional); 228 | assert(items.length == objectsCount); 229 | bench.changeValues(items); 230 | if (!await awaitOrStop(bench.updateMany(items))) { 231 | break; 232 | } 233 | await bench.removeMany(ids); 234 | 235 | await printResult('$_mode: ${i + 1}/$runs finished'); 236 | } 237 | 238 | if (_state == RunState.running) { 239 | _tracker.printTimes(avgOnly: true, functions: [ 240 | 'insertMany', 241 | 'readAll', 242 | 'updateMany', 243 | 'removeMany', 244 | ]); 245 | } 246 | break; 247 | 248 | case Mode.Queries: 249 | final random = Random(); 250 | final operationsCount = int.parse(_operationsController.value.text); 251 | await printResult('Preparing data...'); 252 | final inserts = bench.prepareData(objectsCount); 253 | if (!await awaitOrStop(bench.insertMany(inserts))) { 254 | break; 255 | } 256 | 257 | final relBench = await bench.createRelBenchmark(); 258 | // About 9 sources have the same target 259 | // Ensure target count is uneven to not align with odd/even int values. 260 | final targetCount = objectsCount ~/ 10; 261 | final relTargetsCount = 262 | max(1, targetCount.isEven ? targetCount - 1 : targetCount); 263 | await relBench.insertData(objectsCount, relTargetsCount); 264 | final distinctSourceStrings = 265 | ExecutorBaseRel.distinctSourceStrings(objectsCount); 266 | debugPrint( 267 | "source groups = $distinctSourceStrings, targets = $relTargetsCount"); 268 | 269 | final resultCounts = List.filled(3, -1); 270 | 271 | for (var i = 0; i < runs && _state == RunState.running; i++) { 272 | final qStringValues = List.generate(operationsCount, 273 | (_) => inserts[random.nextInt(objectsCount)].tString, 274 | growable: false); 275 | final qStringMatching = 276 | await bench.queryStringEquals(qStringValues); 277 | assert(qStringMatching.length == 1); 278 | 279 | final qLinkConfigs = List.generate(operationsCount, (_) { 280 | // Ensures 5-6 results (depending on how many int condition filters). 281 | // Also see prepareDataSources function in executor. 282 | final number = random.nextInt(distinctSourceStrings); 283 | return ConfigQueryWithLinks('Source group #$number', 284 | random.nextInt(2), 'Target #$number'); 285 | }, growable: false); 286 | final relResults = await relBench.queryWithLinks(qLinkConfigs); 287 | RangeError.checkValueInInterval( 288 | relResults.length, 5, 6, 'queryWithLinks results length'); 289 | 290 | await printResult('$_mode: ${i + 1}/$runs finished'); 291 | 292 | resultCounts[0] = qStringMatching.length; 293 | resultCounts[1] = relResults.length; 294 | } 295 | 296 | if (_state == RunState.running) { 297 | _tracker.printTimes(avgOnly: true, functions: [ 298 | 'queryStringEquals', 299 | 'queryWithLinks', 300 | ]); 301 | 302 | _print(['', '']); 303 | _print(['', 'Count']); 304 | _print(['queryStringEquals', resultCounts[0].toString()]); 305 | _print(['queryWithLinks', resultCounts[1].toString()]); 306 | 307 | // just so that the test after benchmarks passes 308 | await bench.removeMany(inserts.map((e) => e.id).toList()); 309 | } 310 | 311 | await relBench.close(); 312 | 313 | break; 314 | case Mode.QueryById: 315 | final random = Random(); 316 | final resultsCount = int.parse(_resultsController.value.text); 317 | 318 | await printResult('Preparing data...'); 319 | final inserts = bench.prepareData(objectsCount); 320 | if (!await awaitOrStop(bench.insertMany(inserts))) { 321 | break; 322 | } 323 | 324 | final ids = inserts.map((e) => e.id).toList(growable: false); 325 | 326 | randomSlice(List list, int length) { 327 | final start = list.length == length 328 | ? 0 329 | : random.nextInt(list.length - length); 330 | final result = list.sublist(start, start + length); 331 | assert(result.length == length); 332 | return result; 333 | } 334 | 335 | int resultCount = 0; 336 | for (var i = 0; i < runs && _state == RunState.running; i++) { 337 | final idsShuffled = (ids.toList(growable: false))..shuffle(random); 338 | final randomSliceLength = min(ids.length, resultsCount); 339 | final qByIdItems = await bench.queryById( 340 | randomSlice(idsShuffled, randomSliceLength), '(random)'); 341 | final qByIdItems2 = 342 | await bench.queryById(randomSlice(ids, randomSliceLength)); 343 | assert(qByIdItems.length == qByIdItems2.length); 344 | 345 | await printResult('$_mode: ${i + 1}/$runs finished'); 346 | 347 | resultCount = qByIdItems.length; 348 | } 349 | 350 | if (_state == RunState.running) { 351 | _tracker.printTimes(avgOnly: true, functions: [ 352 | 'queryById', 353 | 'queryById(random)', 354 | ]); 355 | 356 | _print(['', '']); 357 | _print(['', 'Count']); 358 | _print(['queryById', resultCount.toString()]); 359 | 360 | // just so that the test after benchmarks passes 361 | await bench.removeMany(inserts.map((e) => e.id).toList()); 362 | } 363 | 364 | break; 365 | } 366 | } catch (e) { 367 | setState(() { 368 | _result = "Benchmark failed: $e"; 369 | _state = RunState.idle; 370 | }); 371 | rethrow; 372 | } 373 | 374 | // Sanity check after the benchmark: subsequent runs must have same results. 375 | assert(await _testBenchmark(bench)); 376 | 377 | if (_state == RunState.stopping) { 378 | setState(() { 379 | _result = 'Benchmark stopped'; 380 | _resultRows.clear(); 381 | }); 382 | } 383 | 384 | setState(() { 385 | _state = RunState.idle; 386 | }); 387 | } 388 | 389 | Future _testBenchmark(ExecutorBase bench) async { 390 | try { 391 | const count = 100; 392 | await bench.test( 393 | count: count, 394 | qString: _mode == Mode.Queries 395 | ? bench.prepareData((count / 2).floor()).last.tString 396 | : null); 397 | } catch (e) { 398 | setState(() { 399 | _result = "Executor test failed: $e"; 400 | }); 401 | } 402 | return true; 403 | } 404 | 405 | @override 406 | Widget build(BuildContext context) { 407 | // This method is rerun every time setState is called, for instance as done 408 | // by the _incrementCounter method above. 409 | // 410 | // The Flutter framework has been optimized to make rerunning build methods 411 | // fast, so that you can just rebuild anything that needs updating rather 412 | // than having to individually change instances of widgets. 413 | return Scaffold( 414 | appBar: AppBar( 415 | // Here we take the value from the MyHomePage object that was created by 416 | // the App.build method, and use it to set our appbar title. 417 | title: Text(widget.title), 418 | ), 419 | body: Center( 420 | // Center is a layout widget. It takes a single child and positions it 421 | // in the middle of the parent. 422 | child: Column( 423 | mainAxisAlignment: MainAxisAlignment.center, 424 | children: [ 425 | Row(children: [ 426 | const Spacer(), 427 | DropdownButton( 428 | value: _db, 429 | items: enumDropDownItems(DbEngine.values), 430 | onChanged: (DbEngine? value) => 431 | configure(value!, _mode, _indexed)), 432 | const Spacer(), 433 | DropdownButton( 434 | value: _mode, 435 | // TODO items: enumDropDownItems(Mode.values), 436 | // Isar queries can't be evaluated yet because the model 437 | // doesn't work relations. 438 | // Note: evaluating just stringEquals() isn't an option 439 | // because it would be heavily optimized by the VM if 440 | // it's the only function executed and wouldn't be 441 | // comparable to other databases that execute other 442 | // benchmarks in the same loop. 443 | items: enumDropDownItems(Mode.values 444 | .where((mode) => 445 | _db != DbEngine.IsarSync || 446 | (mode != Mode.Queries && mode != Mode.QueryById)) 447 | .toList()), 448 | onChanged: (Mode? value) => configure(_db, value!, _indexed)), 449 | const Spacer(), 450 | const Text('Index'), 451 | if (_db == DbEngine.Hive) 452 | const Text(' not available') 453 | else 454 | Switch( 455 | value: _indexed, 456 | onChanged: (bool value) => configure(_db, _mode, value), 457 | activeTrackColor: Colors.yellow, 458 | activeColor: Colors.orangeAccent, 459 | ), 460 | const Spacer(), 461 | ]), 462 | Row(children: [ 463 | const Spacer(), 464 | Expanded( 465 | child: TextField( 466 | keyboardType: TextInputType.number, 467 | controller: _runsController, 468 | decoration: const InputDecoration( 469 | labelText: 'Runs', 470 | ), 471 | )), 472 | if (_mode == Mode.Queries) const Spacer(), 473 | if (_mode == Mode.Queries) 474 | Expanded( 475 | child: TextField( 476 | keyboardType: TextInputType.number, 477 | controller: _operationsController, 478 | decoration: const InputDecoration( 479 | labelText: 'Operations', 480 | ), 481 | )), 482 | if (_mode == Mode.QueryById) const Spacer(), 483 | if (_mode == Mode.QueryById) 484 | Expanded( 485 | child: TextField( 486 | keyboardType: TextInputType.number, 487 | controller: _resultsController, 488 | decoration: const InputDecoration( 489 | labelText: 'Results', 490 | ), 491 | )), 492 | const Spacer(), 493 | Expanded( 494 | child: TextField( 495 | keyboardType: TextInputType.number, 496 | controller: _objectsController, 497 | decoration: const InputDecoration( 498 | labelText: 'Objects', 499 | ), 500 | )), 501 | const Spacer(), 502 | ]), 503 | const Spacer(), 504 | Text(_result), 505 | const Spacer(), 506 | Container( 507 | padding: const EdgeInsets.symmetric(horizontal: 30), 508 | child: Table( 509 | border: const TableBorder( 510 | horizontalInside: BorderSide(color: Color(0x55000000))), 511 | children: _resultRows)), 512 | const Spacer(), 513 | ], 514 | ), 515 | ), 516 | floatingActionButton: _state == RunState.stopping 517 | ? null 518 | : _state == RunState.running 519 | ? FloatingActionButton( 520 | onPressed: _stopBenchmark, 521 | tooltip: 'Stop', 522 | child: const Icon(Icons.stop), 523 | ) 524 | : FloatingActionButton( 525 | onPressed: _runBenchmark, 526 | tooltip: 'Start', 527 | child: const Icon(Icons.play_arrow), 528 | ), // This trailing comma makes auto-formatting nicer for build methods. 529 | ); 530 | } 531 | } 532 | 533 | List> enumDropDownItems(List values) => values 534 | .map((dynamic e) => DropdownMenuItem( 535 | value: e, 536 | child: 537 | Text(e.toString().substring(e.runtimeType.toString().length + 1)), 538 | )) 539 | .toList(); 540 | -------------------------------------------------------------------------------- /lib/model.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | import 'package:objectbox/objectbox.dart' as obx; 3 | 4 | part 'model.g.dart'; 5 | 6 | abstract class EntityWithSettableId { 7 | int get id; 8 | 9 | set id(int value); 10 | } 11 | 12 | abstract class TestEntity extends EntityWithSettableId { 13 | String get tString; 14 | 15 | int get tInt; // 32-bit 16 | 17 | int get tLong; // 64-bit 18 | set tLong(int value); 19 | 20 | double get tDouble; 21 | 22 | static Map toMap(TestEntity object) => { 23 | 'id': object.id == 0 ? null : object.id, 24 | 'tString': object.tString, 25 | 'tInt': object.tInt, 26 | 'tLong': object.tLong, 27 | 'tDouble': object.tDouble 28 | }; 29 | } 30 | 31 | @obx.Entity() 32 | @HiveType(typeId: 1) 33 | class TestEntityPlain implements TestEntity { 34 | @override 35 | @HiveField(0) 36 | int id; 37 | 38 | @override 39 | @HiveField(1) 40 | String tString; 41 | 42 | @override 43 | @obx.Property(type: obx.PropertyType.int) 44 | @HiveField(2) 45 | int tInt; // 32-bit 46 | 47 | @override 48 | @HiveField(3) 49 | int tLong; // 64-bit 50 | 51 | @override 52 | @HiveField(4) 53 | double tDouble; 54 | 55 | TestEntityPlain(this.id, this.tString, this.tInt, this.tLong, this.tDouble); 56 | 57 | static TestEntityPlain fromMap(Map map) => TestEntityPlain( 58 | map['id'] ?? 0, 59 | map['tString'], 60 | map['tInt'], 61 | map['tLong'], 62 | map['tDouble']); 63 | } 64 | 65 | // A separate entity for queried data so that indexes don't change CRUD results. 66 | @obx.Entity() 67 | class TestEntityIndexed implements TestEntity { 68 | @override 69 | int id; 70 | 71 | @override 72 | @obx.Index() 73 | String tString; 74 | 75 | @override 76 | @obx.Index() 77 | @obx.Property(type: obx.PropertyType.int) 78 | int tInt; // 32-bit 79 | 80 | @override 81 | int tLong; // 64-bit 82 | 83 | @override 84 | double tDouble; 85 | 86 | TestEntityIndexed(this.id, this.tString, this.tInt, this.tLong, this.tDouble); 87 | 88 | static TestEntityIndexed fromMap(Map map) => 89 | TestEntityIndexed(map['id'] ?? 0, map['tString'], map['tInt'], 90 | map['tLong'], map['tDouble']); 91 | } 92 | 93 | abstract class RelSourceEntity extends EntityWithSettableId { 94 | @override 95 | int get id; 96 | 97 | @override 98 | set id(int value); 99 | 100 | String get tString; 101 | 102 | int get tLong; 103 | 104 | // for hive & sqflite 105 | int get relTargetId; 106 | 107 | static Map toMap(RelSourceEntity object) => 108 | { 109 | 'id': object.id == 0 ? null : object.id, 110 | 'tString': object.tString, 111 | 'tLong': object.tLong, 112 | 'relTargetId': object.relTargetId, 113 | }; 114 | } 115 | 116 | @obx.Entity() 117 | @HiveType(typeId: 2) 118 | class RelSourceEntityPlain implements RelSourceEntity { 119 | @override 120 | @HiveField(0) 121 | int id; 122 | 123 | @override 124 | @HiveField(1) 125 | final String tString; 126 | 127 | @override 128 | @HiveField(2) 129 | final int tLong; // 64-bit 130 | 131 | final obxRelTarget = obx.ToOne(); 132 | 133 | @override 134 | @obx.Transient() 135 | @HiveField(3) 136 | final int relTargetId; 137 | 138 | // Note: constructor arg types must match with fromMap used by sqflite. 139 | RelSourceEntityPlain(this.id, this.tString, this.tLong, 140 | [this.relTargetId = 0]) { 141 | obxRelTarget.targetId = relTargetId; 142 | } 143 | 144 | RelSourceEntityPlain.forInsert( 145 | this.tString, this.tLong, RelTargetEntity? relTarget) 146 | : id = 0, 147 | relTargetId = relTarget?.id ?? 0 { 148 | obxRelTarget.targetId = relTargetId; 149 | } 150 | 151 | static RelSourceEntityPlain fromMap(Map map) => 152 | RelSourceEntityPlain( 153 | map['id'] ?? 0, map['tString'], map['tLong'], map['relTargetId']); 154 | } 155 | 156 | @obx.Entity() 157 | class RelSourceEntityIndexed implements RelSourceEntity { 158 | @override 159 | int id; 160 | 161 | @override 162 | @obx.Index() 163 | final String tString; 164 | 165 | @override 166 | final int tLong; // 64-bit 167 | 168 | final obxRelTarget = obx.ToOne(); 169 | 170 | @override 171 | @obx.Transient() 172 | final int relTargetId; 173 | 174 | // Note: constructor arg types must match with fromMap used by sqflite. 175 | RelSourceEntityIndexed(this.id, this.tString, this.tLong, 176 | [this.relTargetId = 0]) { 177 | obxRelTarget.targetId = relTargetId; 178 | } 179 | 180 | RelSourceEntityIndexed.forInsert( 181 | this.tString, this.tLong, RelTargetEntity? relTarget) 182 | : id = 0, 183 | relTargetId = relTarget?.id ?? 0 { 184 | obxRelTarget.targetId = relTargetId; 185 | } 186 | 187 | static RelSourceEntityIndexed fromMap(Map map) => 188 | RelSourceEntityIndexed( 189 | map['id'] ?? 0, map['tString'], map['tLong'], map['relTargetId']); 190 | } 191 | 192 | @obx.Entity() 193 | @HiveType(typeId: 4) 194 | class RelTargetEntity extends EntityWithSettableId { 195 | @override 196 | @HiveField(0) 197 | int id; 198 | 199 | @obx.Index() 200 | @HiveField(1) 201 | String name; 202 | 203 | RelTargetEntity(this.id, this.name); 204 | 205 | static Map toMap(RelTargetEntity object) => 206 | { 207 | 'id': object.id == 0 ? null : object.id, 208 | 'name': object.name 209 | }; 210 | 211 | static RelTargetEntity fromMap(Map map) => 212 | RelTargetEntity(map['id'] ?? 0, map['name']); 213 | } 214 | -------------------------------------------------------------------------------- /lib/model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'model.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class TestEntityPlainAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 1; 12 | 13 | @override 14 | TestEntityPlain read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return TestEntityPlain( 20 | fields[0] as int, 21 | fields[1] as String, 22 | fields[2] as int, 23 | fields[3] as int, 24 | fields[4] as double, 25 | ); 26 | } 27 | 28 | @override 29 | void write(BinaryWriter writer, TestEntityPlain obj) { 30 | writer 31 | ..writeByte(5) 32 | ..writeByte(0) 33 | ..write(obj.id) 34 | ..writeByte(1) 35 | ..write(obj.tString) 36 | ..writeByte(2) 37 | ..write(obj.tInt) 38 | ..writeByte(3) 39 | ..write(obj.tLong) 40 | ..writeByte(4) 41 | ..write(obj.tDouble); 42 | } 43 | 44 | @override 45 | int get hashCode => typeId.hashCode; 46 | 47 | @override 48 | bool operator ==(Object other) => 49 | identical(this, other) || 50 | other is TestEntityPlainAdapter && 51 | runtimeType == other.runtimeType && 52 | typeId == other.typeId; 53 | } 54 | 55 | class RelSourceEntityPlainAdapter extends TypeAdapter { 56 | @override 57 | final int typeId = 2; 58 | 59 | @override 60 | RelSourceEntityPlain read(BinaryReader reader) { 61 | final numOfFields = reader.readByte(); 62 | final fields = { 63 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 64 | }; 65 | return RelSourceEntityPlain( 66 | fields[0] as int, 67 | fields[1] as String, 68 | fields[2] as int, 69 | fields[3] as int, 70 | ); 71 | } 72 | 73 | @override 74 | void write(BinaryWriter writer, RelSourceEntityPlain obj) { 75 | writer 76 | ..writeByte(4) 77 | ..writeByte(0) 78 | ..write(obj.id) 79 | ..writeByte(1) 80 | ..write(obj.tString) 81 | ..writeByte(2) 82 | ..write(obj.tLong) 83 | ..writeByte(3) 84 | ..write(obj.relTargetId); 85 | } 86 | 87 | @override 88 | int get hashCode => typeId.hashCode; 89 | 90 | @override 91 | bool operator ==(Object other) => 92 | identical(this, other) || 93 | other is RelSourceEntityPlainAdapter && 94 | runtimeType == other.runtimeType && 95 | typeId == other.typeId; 96 | } 97 | 98 | class RelTargetEntityAdapter extends TypeAdapter { 99 | @override 100 | final int typeId = 4; 101 | 102 | @override 103 | RelTargetEntity read(BinaryReader reader) { 104 | final numOfFields = reader.readByte(); 105 | final fields = { 106 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 107 | }; 108 | return RelTargetEntity( 109 | fields[0] as int, 110 | fields[1] as String, 111 | ); 112 | } 113 | 114 | @override 115 | void write(BinaryWriter writer, RelTargetEntity obj) { 116 | writer 117 | ..writeByte(2) 118 | ..writeByte(0) 119 | ..write(obj.id) 120 | ..writeByte(1) 121 | ..write(obj.name); 122 | } 123 | 124 | @override 125 | int get hashCode => typeId.hashCode; 126 | 127 | @override 128 | bool operator ==(Object other) => 129 | identical(this, other) || 130 | other is RelTargetEntityAdapter && 131 | runtimeType == other.runtimeType && 132 | typeId == other.typeId; 133 | } 134 | -------------------------------------------------------------------------------- /lib/objectbox-model.json: -------------------------------------------------------------------------------- 1 | { 2 | "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", 3 | "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", 4 | "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", 5 | "entities": [ 6 | { 7 | "id": "2:5453298016524400943", 8 | "lastPropertyId": "5:8933220997389638466", 9 | "name": "TestEntityIndexed", 10 | "properties": [ 11 | { 12 | "id": "1:6045856831974807584", 13 | "name": "id", 14 | "type": 6, 15 | "flags": 1 16 | }, 17 | { 18 | "id": "2:6936912195487802902", 19 | "name": "tString", 20 | "type": 9, 21 | "flags": 2048, 22 | "indexId": "1:3627912493679987388" 23 | }, 24 | { 25 | "id": "3:2740551240924923452", 26 | "name": "tInt", 27 | "type": 5, 28 | "flags": 8, 29 | "indexId": "2:155116997669208311" 30 | }, 31 | { 32 | "id": "4:5923275911778414554", 33 | "name": "tLong", 34 | "type": 6 35 | }, 36 | { 37 | "id": "5:8933220997389638466", 38 | "name": "tDouble", 39 | "type": 8 40 | } 41 | ], 42 | "relations": [] 43 | }, 44 | { 45 | "id": "3:5087630674825288337", 46 | "lastPropertyId": "5:2633769713742296285", 47 | "name": "TestEntityPlain", 48 | "properties": [ 49 | { 50 | "id": "1:4900264929263122750", 51 | "name": "id", 52 | "type": 6, 53 | "flags": 1 54 | }, 55 | { 56 | "id": "2:744298398557394245", 57 | "name": "tString", 58 | "type": 9 59 | }, 60 | { 61 | "id": "3:8517120886012576776", 62 | "name": "tInt", 63 | "type": 5 64 | }, 65 | { 66 | "id": "4:4690018982222267350", 67 | "name": "tLong", 68 | "type": 6 69 | }, 70 | { 71 | "id": "5:2633769713742296285", 72 | "name": "tDouble", 73 | "type": 8 74 | } 75 | ], 76 | "relations": [] 77 | }, 78 | { 79 | "id": "4:3131756290746376924", 80 | "lastPropertyId": "4:3010204554547165657", 81 | "name": "RelSourceEntityIndexed", 82 | "properties": [ 83 | { 84 | "id": "1:857073402609537123", 85 | "name": "id", 86 | "type": 6, 87 | "flags": 1 88 | }, 89 | { 90 | "id": "2:8422124894128951003", 91 | "name": "tString", 92 | "type": 9, 93 | "flags": 2048, 94 | "indexId": "3:1267973680746353216" 95 | }, 96 | { 97 | "id": "3:4831158543554459278", 98 | "name": "tLong", 99 | "type": 6 100 | }, 101 | { 102 | "id": "4:3010204554547165657", 103 | "name": "obxRelTargetId", 104 | "type": 11, 105 | "flags": 520, 106 | "indexId": "4:1067902231777022849", 107 | "relationTarget": "RelTargetEntity" 108 | } 109 | ], 110 | "relations": [] 111 | }, 112 | { 113 | "id": "5:6999419589241537986", 114 | "lastPropertyId": "4:2940873808771910881", 115 | "name": "RelSourceEntityPlain", 116 | "properties": [ 117 | { 118 | "id": "1:2178707517515063163", 119 | "name": "id", 120 | "type": 6, 121 | "flags": 1 122 | }, 123 | { 124 | "id": "2:6759259138758744038", 125 | "name": "tString", 126 | "type": 9 127 | }, 128 | { 129 | "id": "3:8644747147683565072", 130 | "name": "tLong", 131 | "type": 6 132 | }, 133 | { 134 | "id": "4:2940873808771910881", 135 | "name": "obxRelTargetId", 136 | "type": 11, 137 | "flags": 520, 138 | "indexId": "5:1393076386577997042", 139 | "relationTarget": "RelTargetEntity" 140 | } 141 | ], 142 | "relations": [] 143 | }, 144 | { 145 | "id": "6:3460633979791451003", 146 | "lastPropertyId": "2:8889103133672686790", 147 | "name": "RelTargetEntity", 148 | "properties": [ 149 | { 150 | "id": "1:8661863255381416471", 151 | "name": "id", 152 | "type": 6, 153 | "flags": 1 154 | }, 155 | { 156 | "id": "2:8889103133672686790", 157 | "name": "name", 158 | "type": 9, 159 | "flags": 2048, 160 | "indexId": "6:1013178598434110616" 161 | } 162 | ], 163 | "relations": [] 164 | } 165 | ], 166 | "lastEntityId": "6:3460633979791451003", 167 | "lastIndexId": "6:1013178598434110616", 168 | "lastRelationId": "0:0", 169 | "lastSequenceId": "0:0", 170 | "modelVersion": 5, 171 | "modelVersionParserMinimum": 5, 172 | "retiredEntityUids": [ 173 | 1824294286181366916 174 | ], 175 | "retiredIndexUids": [], 176 | "retiredPropertyUids": [ 177 | 725236837183566145, 178 | 3060128101942237916, 179 | 3092165041934597703, 180 | 4958684909838795382, 181 | 5062831200094422416 182 | ], 183 | "retiredRelationUids": [], 184 | "version": 1 185 | } -------------------------------------------------------------------------------- /lib/objectbox.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // This code was generated by ObjectBox. To update it run the generator again 3 | // with `dart run build_runner build`. 4 | // See also https://docs.objectbox.io/getting-started#generate-objectbox-code 5 | 6 | // ignore_for_file: camel_case_types, depend_on_referenced_packages 7 | // coverage:ignore-file 8 | 9 | import 'dart:typed_data'; 10 | 11 | import 'package:flat_buffers/flat_buffers.dart' as fb; 12 | import 'package:objectbox/internal.dart' 13 | as obx_int; // generated code can access "internal" functionality 14 | import 'package:objectbox/objectbox.dart' as obx; 15 | import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart'; 16 | 17 | import 'model.dart'; 18 | 19 | export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file 20 | 21 | final _entities = [ 22 | obx_int.ModelEntity( 23 | id: const obx_int.IdUid(2, 5453298016524400943), 24 | name: 'TestEntityIndexed', 25 | lastPropertyId: const obx_int.IdUid(5, 8933220997389638466), 26 | flags: 0, 27 | properties: [ 28 | obx_int.ModelProperty( 29 | id: const obx_int.IdUid(1, 6045856831974807584), 30 | name: 'id', 31 | type: 6, 32 | flags: 1), 33 | obx_int.ModelProperty( 34 | id: const obx_int.IdUid(2, 6936912195487802902), 35 | name: 'tString', 36 | type: 9, 37 | flags: 2048, 38 | indexId: const obx_int.IdUid(1, 3627912493679987388)), 39 | obx_int.ModelProperty( 40 | id: const obx_int.IdUid(3, 2740551240924923452), 41 | name: 'tInt', 42 | type: 5, 43 | flags: 8, 44 | indexId: const obx_int.IdUid(2, 155116997669208311)), 45 | obx_int.ModelProperty( 46 | id: const obx_int.IdUid(4, 5923275911778414554), 47 | name: 'tLong', 48 | type: 6, 49 | flags: 0), 50 | obx_int.ModelProperty( 51 | id: const obx_int.IdUid(5, 8933220997389638466), 52 | name: 'tDouble', 53 | type: 8, 54 | flags: 0) 55 | ], 56 | relations: [], 57 | backlinks: []), 58 | obx_int.ModelEntity( 59 | id: const obx_int.IdUid(3, 5087630674825288337), 60 | name: 'TestEntityPlain', 61 | lastPropertyId: const obx_int.IdUid(5, 2633769713742296285), 62 | flags: 0, 63 | properties: [ 64 | obx_int.ModelProperty( 65 | id: const obx_int.IdUid(1, 4900264929263122750), 66 | name: 'id', 67 | type: 6, 68 | flags: 1), 69 | obx_int.ModelProperty( 70 | id: const obx_int.IdUid(2, 744298398557394245), 71 | name: 'tString', 72 | type: 9, 73 | flags: 0), 74 | obx_int.ModelProperty( 75 | id: const obx_int.IdUid(3, 8517120886012576776), 76 | name: 'tInt', 77 | type: 5, 78 | flags: 0), 79 | obx_int.ModelProperty( 80 | id: const obx_int.IdUid(4, 4690018982222267350), 81 | name: 'tLong', 82 | type: 6, 83 | flags: 0), 84 | obx_int.ModelProperty( 85 | id: const obx_int.IdUid(5, 2633769713742296285), 86 | name: 'tDouble', 87 | type: 8, 88 | flags: 0) 89 | ], 90 | relations: [], 91 | backlinks: []), 92 | obx_int.ModelEntity( 93 | id: const obx_int.IdUid(4, 3131756290746376924), 94 | name: 'RelSourceEntityIndexed', 95 | lastPropertyId: const obx_int.IdUid(4, 3010204554547165657), 96 | flags: 0, 97 | properties: [ 98 | obx_int.ModelProperty( 99 | id: const obx_int.IdUid(1, 857073402609537123), 100 | name: 'id', 101 | type: 6, 102 | flags: 1), 103 | obx_int.ModelProperty( 104 | id: const obx_int.IdUid(2, 8422124894128951003), 105 | name: 'tString', 106 | type: 9, 107 | flags: 2048, 108 | indexId: const obx_int.IdUid(3, 1267973680746353216)), 109 | obx_int.ModelProperty( 110 | id: const obx_int.IdUid(3, 4831158543554459278), 111 | name: 'tLong', 112 | type: 6, 113 | flags: 0), 114 | obx_int.ModelProperty( 115 | id: const obx_int.IdUid(4, 3010204554547165657), 116 | name: 'obxRelTargetId', 117 | type: 11, 118 | flags: 520, 119 | indexId: const obx_int.IdUid(4, 1067902231777022849), 120 | relationTarget: 'RelTargetEntity') 121 | ], 122 | relations: [], 123 | backlinks: []), 124 | obx_int.ModelEntity( 125 | id: const obx_int.IdUid(5, 6999419589241537986), 126 | name: 'RelSourceEntityPlain', 127 | lastPropertyId: const obx_int.IdUid(4, 2940873808771910881), 128 | flags: 0, 129 | properties: [ 130 | obx_int.ModelProperty( 131 | id: const obx_int.IdUid(1, 2178707517515063163), 132 | name: 'id', 133 | type: 6, 134 | flags: 1), 135 | obx_int.ModelProperty( 136 | id: const obx_int.IdUid(2, 6759259138758744038), 137 | name: 'tString', 138 | type: 9, 139 | flags: 0), 140 | obx_int.ModelProperty( 141 | id: const obx_int.IdUid(3, 8644747147683565072), 142 | name: 'tLong', 143 | type: 6, 144 | flags: 0), 145 | obx_int.ModelProperty( 146 | id: const obx_int.IdUid(4, 2940873808771910881), 147 | name: 'obxRelTargetId', 148 | type: 11, 149 | flags: 520, 150 | indexId: const obx_int.IdUid(5, 1393076386577997042), 151 | relationTarget: 'RelTargetEntity') 152 | ], 153 | relations: [], 154 | backlinks: []), 155 | obx_int.ModelEntity( 156 | id: const obx_int.IdUid(6, 3460633979791451003), 157 | name: 'RelTargetEntity', 158 | lastPropertyId: const obx_int.IdUid(2, 8889103133672686790), 159 | flags: 0, 160 | properties: [ 161 | obx_int.ModelProperty( 162 | id: const obx_int.IdUid(1, 8661863255381416471), 163 | name: 'id', 164 | type: 6, 165 | flags: 1), 166 | obx_int.ModelProperty( 167 | id: const obx_int.IdUid(2, 8889103133672686790), 168 | name: 'name', 169 | type: 9, 170 | flags: 2048, 171 | indexId: const obx_int.IdUid(6, 1013178598434110616)) 172 | ], 173 | relations: [], 174 | backlinks: []) 175 | ]; 176 | 177 | /// Shortcut for [obx.Store.new] that passes [getObjectBoxModel] and for Flutter 178 | /// apps by default a [directory] using `defaultStoreDirectory()` from the 179 | /// ObjectBox Flutter library. 180 | /// 181 | /// Note: for desktop apps it is recommended to specify a unique [directory]. 182 | /// 183 | /// See [obx.Store.new] for an explanation of all parameters. 184 | /// 185 | /// For Flutter apps, also calls `loadObjectBoxLibraryAndroidCompat()` from 186 | /// the ObjectBox Flutter library to fix loading the native ObjectBox library 187 | /// on Android 6 and older. 188 | Future openStore( 189 | {String? directory, 190 | int? maxDBSizeInKB, 191 | int? maxDataSizeInKB, 192 | int? fileMode, 193 | int? maxReaders, 194 | bool queriesCaseSensitiveDefault = true, 195 | String? macosApplicationGroup}) async { 196 | await loadObjectBoxLibraryAndroidCompat(); 197 | return obx.Store(getObjectBoxModel(), 198 | directory: directory ?? (await defaultStoreDirectory()).path, 199 | maxDBSizeInKB: maxDBSizeInKB, 200 | maxDataSizeInKB: maxDataSizeInKB, 201 | fileMode: fileMode, 202 | maxReaders: maxReaders, 203 | queriesCaseSensitiveDefault: queriesCaseSensitiveDefault, 204 | macosApplicationGroup: macosApplicationGroup); 205 | } 206 | 207 | /// Returns the ObjectBox model definition for this project for use with 208 | /// [obx.Store.new]. 209 | obx_int.ModelDefinition getObjectBoxModel() { 210 | final model = obx_int.ModelInfo( 211 | entities: _entities, 212 | lastEntityId: const obx_int.IdUid(6, 3460633979791451003), 213 | lastIndexId: const obx_int.IdUid(6, 1013178598434110616), 214 | lastRelationId: const obx_int.IdUid(0, 0), 215 | lastSequenceId: const obx_int.IdUid(0, 0), 216 | retiredEntityUids: const [1824294286181366916], 217 | retiredIndexUids: const [], 218 | retiredPropertyUids: const [ 219 | 725236837183566145, 220 | 3060128101942237916, 221 | 3092165041934597703, 222 | 4958684909838795382, 223 | 5062831200094422416 224 | ], 225 | retiredRelationUids: const [], 226 | modelVersion: 5, 227 | modelVersionParserMinimum: 5, 228 | version: 1); 229 | 230 | final bindings = { 231 | TestEntityIndexed: obx_int.EntityDefinition( 232 | model: _entities[0], 233 | toOneRelations: (TestEntityIndexed object) => [], 234 | toManyRelations: (TestEntityIndexed object) => {}, 235 | getId: (TestEntityIndexed object) => object.id, 236 | setId: (TestEntityIndexed object, int id) { 237 | object.id = id; 238 | }, 239 | objectToFB: (TestEntityIndexed object, fb.Builder fbb) { 240 | final tStringOffset = fbb.writeString(object.tString); 241 | fbb.startTable(6); 242 | fbb.addInt64(0, object.id); 243 | fbb.addOffset(1, tStringOffset); 244 | fbb.addInt32(2, object.tInt); 245 | fbb.addInt64(3, object.tLong); 246 | fbb.addFloat64(4, object.tDouble); 247 | fbb.finish(fbb.endTable()); 248 | return object.id; 249 | }, 250 | objectFromFB: (obx.Store store, ByteData fbData) { 251 | final buffer = fb.BufferContext(fbData); 252 | final rootOffset = buffer.derefObject(0); 253 | final idParam = 254 | const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); 255 | final tStringParam = const fb.StringReader(asciiOptimization: true) 256 | .vTableGet(buffer, rootOffset, 6, ''); 257 | final tIntParam = 258 | const fb.Int32Reader().vTableGet(buffer, rootOffset, 8, 0); 259 | final tLongParam = 260 | const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0); 261 | final tDoubleParam = 262 | const fb.Float64Reader().vTableGet(buffer, rootOffset, 12, 0); 263 | final object = TestEntityIndexed( 264 | idParam, tStringParam, tIntParam, tLongParam, tDoubleParam); 265 | 266 | return object; 267 | }), 268 | TestEntityPlain: obx_int.EntityDefinition( 269 | model: _entities[1], 270 | toOneRelations: (TestEntityPlain object) => [], 271 | toManyRelations: (TestEntityPlain object) => {}, 272 | getId: (TestEntityPlain object) => object.id, 273 | setId: (TestEntityPlain object, int id) { 274 | object.id = id; 275 | }, 276 | objectToFB: (TestEntityPlain object, fb.Builder fbb) { 277 | final tStringOffset = fbb.writeString(object.tString); 278 | fbb.startTable(6); 279 | fbb.addInt64(0, object.id); 280 | fbb.addOffset(1, tStringOffset); 281 | fbb.addInt32(2, object.tInt); 282 | fbb.addInt64(3, object.tLong); 283 | fbb.addFloat64(4, object.tDouble); 284 | fbb.finish(fbb.endTable()); 285 | return object.id; 286 | }, 287 | objectFromFB: (obx.Store store, ByteData fbData) { 288 | final buffer = fb.BufferContext(fbData); 289 | final rootOffset = buffer.derefObject(0); 290 | final idParam = 291 | const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); 292 | final tStringParam = const fb.StringReader(asciiOptimization: true) 293 | .vTableGet(buffer, rootOffset, 6, ''); 294 | final tIntParam = 295 | const fb.Int32Reader().vTableGet(buffer, rootOffset, 8, 0); 296 | final tLongParam = 297 | const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0); 298 | final tDoubleParam = 299 | const fb.Float64Reader().vTableGet(buffer, rootOffset, 12, 0); 300 | final object = TestEntityPlain( 301 | idParam, tStringParam, tIntParam, tLongParam, tDoubleParam); 302 | 303 | return object; 304 | }), 305 | RelSourceEntityIndexed: obx_int.EntityDefinition( 306 | model: _entities[2], 307 | toOneRelations: (RelSourceEntityIndexed object) => 308 | [object.obxRelTarget], 309 | toManyRelations: (RelSourceEntityIndexed object) => {}, 310 | getId: (RelSourceEntityIndexed object) => object.id, 311 | setId: (RelSourceEntityIndexed object, int id) { 312 | object.id = id; 313 | }, 314 | objectToFB: (RelSourceEntityIndexed object, fb.Builder fbb) { 315 | final tStringOffset = fbb.writeString(object.tString); 316 | fbb.startTable(5); 317 | fbb.addInt64(0, object.id); 318 | fbb.addOffset(1, tStringOffset); 319 | fbb.addInt64(2, object.tLong); 320 | fbb.addInt64(3, object.obxRelTarget.targetId); 321 | fbb.finish(fbb.endTable()); 322 | return object.id; 323 | }, 324 | objectFromFB: (obx.Store store, ByteData fbData) { 325 | final buffer = fb.BufferContext(fbData); 326 | final rootOffset = buffer.derefObject(0); 327 | final idParam = 328 | const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); 329 | final tStringParam = const fb.StringReader(asciiOptimization: true) 330 | .vTableGet(buffer, rootOffset, 6, ''); 331 | final tLongParam = 332 | const fb.Int64Reader().vTableGet(buffer, rootOffset, 8, 0); 333 | final object = 334 | RelSourceEntityIndexed(idParam, tStringParam, tLongParam); 335 | object.obxRelTarget.targetId = 336 | const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0); 337 | object.obxRelTarget.attach(store); 338 | return object; 339 | }), 340 | RelSourceEntityPlain: obx_int.EntityDefinition( 341 | model: _entities[3], 342 | toOneRelations: (RelSourceEntityPlain object) => [object.obxRelTarget], 343 | toManyRelations: (RelSourceEntityPlain object) => {}, 344 | getId: (RelSourceEntityPlain object) => object.id, 345 | setId: (RelSourceEntityPlain object, int id) { 346 | object.id = id; 347 | }, 348 | objectToFB: (RelSourceEntityPlain object, fb.Builder fbb) { 349 | final tStringOffset = fbb.writeString(object.tString); 350 | fbb.startTable(5); 351 | fbb.addInt64(0, object.id); 352 | fbb.addOffset(1, tStringOffset); 353 | fbb.addInt64(2, object.tLong); 354 | fbb.addInt64(3, object.obxRelTarget.targetId); 355 | fbb.finish(fbb.endTable()); 356 | return object.id; 357 | }, 358 | objectFromFB: (obx.Store store, ByteData fbData) { 359 | final buffer = fb.BufferContext(fbData); 360 | final rootOffset = buffer.derefObject(0); 361 | final idParam = 362 | const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); 363 | final tStringParam = const fb.StringReader(asciiOptimization: true) 364 | .vTableGet(buffer, rootOffset, 6, ''); 365 | final tLongParam = 366 | const fb.Int64Reader().vTableGet(buffer, rootOffset, 8, 0); 367 | final object = 368 | RelSourceEntityPlain(idParam, tStringParam, tLongParam); 369 | object.obxRelTarget.targetId = 370 | const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0); 371 | object.obxRelTarget.attach(store); 372 | return object; 373 | }), 374 | RelTargetEntity: obx_int.EntityDefinition( 375 | model: _entities[4], 376 | toOneRelations: (RelTargetEntity object) => [], 377 | toManyRelations: (RelTargetEntity object) => {}, 378 | getId: (RelTargetEntity object) => object.id, 379 | setId: (RelTargetEntity object, int id) { 380 | object.id = id; 381 | }, 382 | objectToFB: (RelTargetEntity object, fb.Builder fbb) { 383 | final nameOffset = fbb.writeString(object.name); 384 | fbb.startTable(3); 385 | fbb.addInt64(0, object.id); 386 | fbb.addOffset(1, nameOffset); 387 | fbb.finish(fbb.endTable()); 388 | return object.id; 389 | }, 390 | objectFromFB: (obx.Store store, ByteData fbData) { 391 | final buffer = fb.BufferContext(fbData); 392 | final rootOffset = buffer.derefObject(0); 393 | final idParam = 394 | const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0); 395 | final nameParam = const fb.StringReader(asciiOptimization: true) 396 | .vTableGet(buffer, rootOffset, 6, ''); 397 | final object = RelTargetEntity(idParam, nameParam); 398 | 399 | return object; 400 | }) 401 | }; 402 | 403 | return obx_int.ModelDefinition(model, bindings); 404 | } 405 | 406 | /// [TestEntityIndexed] entity fields to define ObjectBox queries. 407 | class TestEntityIndexed_ { 408 | /// See [TestEntityIndexed.id]. 409 | static final id = 410 | obx.QueryIntegerProperty(_entities[0].properties[0]); 411 | 412 | /// See [TestEntityIndexed.tString]. 413 | static final tString = 414 | obx.QueryStringProperty(_entities[0].properties[1]); 415 | 416 | /// See [TestEntityIndexed.tInt]. 417 | static final tInt = 418 | obx.QueryIntegerProperty(_entities[0].properties[2]); 419 | 420 | /// See [TestEntityIndexed.tLong]. 421 | static final tLong = 422 | obx.QueryIntegerProperty(_entities[0].properties[3]); 423 | 424 | /// See [TestEntityIndexed.tDouble]. 425 | static final tDouble = 426 | obx.QueryDoubleProperty(_entities[0].properties[4]); 427 | } 428 | 429 | /// [TestEntityPlain] entity fields to define ObjectBox queries. 430 | class TestEntityPlain_ { 431 | /// See [TestEntityPlain.id]. 432 | static final id = 433 | obx.QueryIntegerProperty(_entities[1].properties[0]); 434 | 435 | /// See [TestEntityPlain.tString]. 436 | static final tString = 437 | obx.QueryStringProperty(_entities[1].properties[1]); 438 | 439 | /// See [TestEntityPlain.tInt]. 440 | static final tInt = 441 | obx.QueryIntegerProperty(_entities[1].properties[2]); 442 | 443 | /// See [TestEntityPlain.tLong]. 444 | static final tLong = 445 | obx.QueryIntegerProperty(_entities[1].properties[3]); 446 | 447 | /// See [TestEntityPlain.tDouble]. 448 | static final tDouble = 449 | obx.QueryDoubleProperty(_entities[1].properties[4]); 450 | } 451 | 452 | /// [RelSourceEntityIndexed] entity fields to define ObjectBox queries. 453 | class RelSourceEntityIndexed_ { 454 | /// See [RelSourceEntityIndexed.id]. 455 | static final id = obx.QueryIntegerProperty( 456 | _entities[2].properties[0]); 457 | 458 | /// See [RelSourceEntityIndexed.tString]. 459 | static final tString = obx.QueryStringProperty( 460 | _entities[2].properties[1]); 461 | 462 | /// See [RelSourceEntityIndexed.tLong]. 463 | static final tLong = obx.QueryIntegerProperty( 464 | _entities[2].properties[2]); 465 | 466 | /// See [RelSourceEntityIndexed.obxRelTarget]. 467 | static final obxRelTarget = 468 | obx.QueryRelationToOne( 469 | _entities[2].properties[3]); 470 | } 471 | 472 | /// [RelSourceEntityPlain] entity fields to define ObjectBox queries. 473 | class RelSourceEntityPlain_ { 474 | /// See [RelSourceEntityPlain.id]. 475 | static final id = obx.QueryIntegerProperty( 476 | _entities[3].properties[0]); 477 | 478 | /// See [RelSourceEntityPlain.tString]. 479 | static final tString = 480 | obx.QueryStringProperty(_entities[3].properties[1]); 481 | 482 | /// See [RelSourceEntityPlain.tLong]. 483 | static final tLong = obx.QueryIntegerProperty( 484 | _entities[3].properties[2]); 485 | 486 | /// See [RelSourceEntityPlain.obxRelTarget]. 487 | static final obxRelTarget = 488 | obx.QueryRelationToOne( 489 | _entities[3].properties[3]); 490 | } 491 | 492 | /// [RelTargetEntity] entity fields to define ObjectBox queries. 493 | class RelTargetEntity_ { 494 | /// See [RelTargetEntity.id]. 495 | static final id = 496 | obx.QueryIntegerProperty(_entities[4].properties[0]); 497 | 498 | /// See [RelTargetEntity.name]. 499 | static final name = 500 | obx.QueryStringProperty(_entities[4].properties[1]); 501 | } 502 | -------------------------------------------------------------------------------- /lib/obx_executor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'executor.dart'; 4 | import 'model.dart'; 5 | import 'objectbox.g.dart'; 6 | import 'time_tracker.dart'; 7 | 8 | abstract class Executor extends ExecutorBase { 9 | final Store store; 10 | final Box box; 11 | final Query queryStringEq; 12 | final void Function(String) queryStringEqSetValue; 13 | 14 | Executor._( 15 | super.tracker, this.store, this.queryStringEq, this.queryStringEqSetValue) 16 | : box = store.box(); 17 | 18 | @override 19 | Future close() async => store.close(); 20 | 21 | @override 22 | Future insertMany(List items) => 23 | Future.value(tracker.track('insertMany', () => box.putMany(items))); 24 | 25 | @override 26 | Future updateMany(List items) => 27 | Future.value(tracker.track('updateMany', () => box.putMany(items))); 28 | 29 | @override 30 | Future> readAll(List optionalIds) => 31 | Future.value(tracker.track('readAll', () => box.getAll())); 32 | 33 | @override 34 | Future> queryById(List ids, [String? benchmarkQualifier]) => 35 | Future.value(tracker.track( 36 | 'queryById${benchmarkQualifier ?? ''}', () => box.getMany(ids))); 37 | 38 | @override 39 | Future removeMany(List ids) => 40 | Future.value(tracker.track('removeMany', () => box.removeMany(ids))); 41 | 42 | @override 43 | Future> queryStringEquals(List values) => 44 | Future.value(tracker.track( 45 | 'queryStringEquals', 46 | () => store.runInTransaction(TxMode.read, () { 47 | late List result; 48 | final length = values.length; 49 | for (var i = 0; i < length; i++) { 50 | queryStringEqSetValue(values[i]); 51 | result = queryStringEq.find(); 52 | } 53 | return result; 54 | }))); 55 | } 56 | 57 | /// Using an entity without indexes 58 | class ExecutorPlain extends Executor { 59 | ExecutorPlain._(super.tracker, super.store, super.queryStringEq, 60 | super.queryStringEqSetValue) 61 | : super._(); 62 | 63 | factory ExecutorPlain(Directory dbDir, TimeTracker tracker) { 64 | final store = Store(getObjectBoxModel(), 65 | directory: dbDir.path, 66 | queriesCaseSensitiveDefault: ExecutorBase.caseSensitive); 67 | 68 | final query = store 69 | .box() 70 | .query(TestEntityPlain_.tString.equals('')) 71 | .build(); 72 | final queryParam = query.param(TestEntityPlain_.tString); 73 | queryStringEqSetValue(String val) => queryParam.value = val; 74 | 75 | return ExecutorPlain._(tracker, store, query, queryStringEqSetValue); 76 | } 77 | 78 | @override 79 | TestEntityPlain createEntity( 80 | String tString, int tInt, int tLong, double tDouble) { 81 | return TestEntityPlain(0, tString, tInt, tLong, tDouble); 82 | } 83 | 84 | @override 85 | Future createRelBenchmark() => 86 | Future.value(ExecutorRel(store, tracker)); 87 | } 88 | 89 | /// Using an entity with indexes 90 | class ExecutorIndexed extends Executor { 91 | ExecutorIndexed._(super.tracker, super.store, super.queryStringEq, 92 | super.queryStringEqSetValue) 93 | : super._(); 94 | 95 | factory ExecutorIndexed(Directory dbDir, TimeTracker tracker) { 96 | final store = Store(getObjectBoxModel(), 97 | directory: dbDir.path, 98 | queriesCaseSensitiveDefault: ExecutorBase.caseSensitive); 99 | 100 | final query = store 101 | .box() 102 | .query(TestEntityIndexed_.tString.equals('')) 103 | .build(); 104 | final queryParam = query.param(TestEntityIndexed_.tString); 105 | queryStringEqSetValue(String val) => queryParam.value = val; 106 | 107 | return ExecutorIndexed._(tracker, store, query, queryStringEqSetValue); 108 | } 109 | 110 | @override 111 | TestEntityIndexed createEntity( 112 | String tString, int tInt, int tLong, double tDouble) { 113 | return TestEntityIndexed(0, tString, tInt, tLong, tDouble); 114 | } 115 | 116 | @override 117 | Future createRelBenchmark() => 118 | Future.value(ExecutorRel(store, tracker)); 119 | } 120 | 121 | class ExecutorRel extends ExecutorBaseRel { 122 | final Store store; 123 | final Box box; 124 | final Query query; 125 | final void Function(String, int, String) querySetParams; 126 | 127 | factory ExecutorRel(Store store, TimeTracker tracker) { 128 | late final void Function(String, int, String) querySetParams; 129 | late final Query queryT; 130 | if (T == RelSourceEntityPlain) { 131 | final query = (store.box().query( 132 | RelSourceEntityPlain_.tString.equals('') & 133 | RelSourceEntityPlain_.tLong.equals(0)) 134 | ..link(RelSourceEntityPlain_.obxRelTarget, 135 | RelTargetEntity_.name.equals(''))) 136 | .build(); 137 | final queryParam1 = query.param(RelSourceEntityPlain_.tString); 138 | final queryParam2 = query.param(RelSourceEntityPlain_.tLong); 139 | final queryParam3 = query.param(RelTargetEntity_.name); 140 | querySetParams = (String sourceStringEquals, int sourceIntEquals, 141 | String targetStringEquals) { 142 | queryParam1.value = sourceStringEquals; 143 | queryParam2.value = sourceIntEquals; 144 | queryParam3.value = targetStringEquals; 145 | }; 146 | queryT = query as Query; 147 | } else { 148 | final query = (store.box().query( 149 | RelSourceEntityIndexed_.tString.equals('') & 150 | RelSourceEntityIndexed_.tLong.equals(0)) 151 | ..link(RelSourceEntityIndexed_.obxRelTarget, 152 | RelTargetEntity_.name.equals(''))) 153 | .build(); 154 | final queryParam1 = query.param(RelSourceEntityIndexed_.tString); 155 | final queryParam2 = query.param(RelSourceEntityIndexed_.tLong); 156 | final queryParam3 = query.param(RelTargetEntity_.name); 157 | querySetParams = (String sourceStringEquals, int sourceIntEquals, 158 | String targetStringEquals) { 159 | queryParam1.value = sourceStringEquals; 160 | queryParam2.value = sourceIntEquals; 161 | queryParam3.value = targetStringEquals; 162 | }; 163 | queryT = query as Query; 164 | } 165 | return ExecutorRel._(tracker, store, queryT, querySetParams); 166 | } 167 | 168 | ExecutorRel._(super.tracker, this.store, this.query, this.querySetParams) 169 | : box = store.box(); 170 | 171 | @override 172 | Future close() async { 173 | // Do nothing, store is closed by Executor. 174 | } 175 | 176 | @override 177 | Future insertData(int relSourceCount, int relTargetCount) => 178 | Future.sync(() { 179 | final targets = prepareDataTargets(relTargetCount); 180 | store.box().putMany(targets); 181 | final sources = prepareDataSources(relSourceCount, targets); 182 | box.putMany(sources); 183 | assert(box.count() == relSourceCount); 184 | assert(store.box().count() == relTargetCount); 185 | }); 186 | 187 | @override 188 | Future> queryWithLinks(List args) => 189 | Future.value(tracker.track( 190 | 'queryWithLinks', 191 | () => store.runInTransaction(TxMode.read, () { 192 | late List result; 193 | final length = args.length; 194 | for (var i = 0; i < length; i++) { 195 | querySetParams(args[i].sourceStringEquals, 196 | args[i].sourceIntEquals, args[i].targetStringEquals); 197 | result = query.find(); 198 | } 199 | return result; 200 | }))); 201 | } 202 | -------------------------------------------------------------------------------- /lib/sqf_executor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:sqflite/sqflite.dart'; 4 | 5 | import 'executor.dart'; 6 | import 'model.dart'; 7 | import 'time_tracker.dart'; 8 | 9 | abstract class Executor extends ExecutorBase { 10 | final Database _db; 11 | final String _table; 12 | 13 | final T Function(Map) _fromMap; 14 | 15 | Executor._(this._table, this._db, this._fromMap, TimeTracker tracker) 16 | : super(tracker); 17 | 18 | static Future createDatabase( 19 | Directory dbDir, String table, bool withIndexes) { 20 | return openDatabase(dbDir.path, version: 1, 21 | onCreate: (Database db, int version) async { 22 | await db.execute(''' 23 | CREATE TABLE $table ( 24 | id integer primary key autoincrement, 25 | tString text, 26 | tInt int, 27 | tLong int, 28 | tDouble real) 29 | '''); 30 | if (withIndexes) { 31 | await db.execute('CREATE INDEX ${table}_int ON $table(tInt)'); 32 | await db.execute('CREATE INDEX ${table}_str ON $table(tString ' 33 | '${ExecutorBase.caseSensitive ? '' : 'COLLATE NOCASE'})'); 34 | } 35 | }); 36 | } 37 | 38 | @override 39 | Future close() => _db.close(); 40 | 41 | // TODO use the generic _insertMany() if it doesn't decrease performance 42 | @override 43 | Future insertMany(List items) async => 44 | tracker.trackAsync('insertMany', () async { 45 | final tx = _db.batch(); 46 | for (var object in items) { 47 | tx.insert(_table, TestEntity.toMap(object)); 48 | } 49 | final ids = await tx.commit(); 50 | for (int i = 0; i < ids.length; i++) { 51 | items[i].id = ids[i] as int; 52 | } 53 | }); 54 | 55 | @override 56 | Future updateMany(List items) async => 57 | tracker.trackAsync('updateMany', () async { 58 | final tx = _db.batch(); 59 | for (var object in items) { 60 | tx.update(_table, TestEntity.toMap(object), 61 | where: 'id = ?', whereArgs: [object.id]); 62 | } 63 | await tx.commit(); 64 | }); 65 | 66 | Future> _query(DatabaseExecutor db, 67 | T Function(Map) reader, String where, 68 | [List? whereArgs]) async => 69 | (await db.query(_table, where: where, whereArgs: whereArgs)) 70 | .map(reader) 71 | .toList(); 72 | 73 | @override 74 | Future> readAll(List optionalIds) => tracker.trackAsync( 75 | 'readAll', () async => (await _db.query(_table)).map(_fromMap).toList()); 76 | 77 | @override 78 | Future> queryById(List ids, [String? benchmarkQualifier]) => 79 | tracker.trackAsync('queryById${benchmarkQualifier ?? ''}', 80 | () async => await _query(_db, _fromMap, 'id in (${ids.join(',')})')); 81 | 82 | @override 83 | Future removeMany(List ids) async => tracker.trackAsync( 84 | 'removeMany', 85 | () async => await _db.delete(_table, where: 'id in (${ids.join(',')})')); 86 | 87 | @override 88 | Future> queryStringEquals(List values) => tracker.trackAsync( 89 | 'queryStringEquals', 90 | () async => _db.transaction((txn) async { 91 | late List result; 92 | final length = values.length; 93 | for (var i = 0; i < length; i++) { 94 | result = await _query(txn, _fromMap, 'tString = ?', [values[i]]); 95 | } 96 | return result; 97 | })); 98 | } 99 | 100 | /// Using an entity without indexes 101 | class ExecutorPlain extends Executor { 102 | ExecutorPlain._(super.table, super.db, super.fromMap, super.tracker) 103 | : super._(); 104 | 105 | static Future> create( 106 | Directory dbDir, TimeTracker tracker) async { 107 | final table = (TestEntityPlain).toString(); 108 | return ExecutorPlain._( 109 | table, 110 | await Executor.createDatabase(dbDir, table, false), 111 | TestEntityPlain.fromMap, 112 | tracker); 113 | } 114 | 115 | @override 116 | TestEntityPlain createEntity( 117 | String tString, int tInt, int tLong, double tDouble) { 118 | return TestEntityPlain(0, tString, tInt, tLong, tDouble); 119 | } 120 | 121 | @override 122 | Future createRelBenchmark() => 123 | Future.value(ExecutorRel.create(tracker, _db)); 124 | } 125 | 126 | /// Using an entity with indexes 127 | class ExecutorIndexed extends Executor { 128 | ExecutorIndexed._(super.table, super.db, super.fromMap, super.tracker) 129 | : super._(); 130 | 131 | static Future> create( 132 | Directory dbDir, TimeTracker tracker) async { 133 | final table = (TestEntityIndexed).toString(); 134 | return ExecutorIndexed._( 135 | table, 136 | await Executor.createDatabase(dbDir, table, true), 137 | TestEntityIndexed.fromMap, 138 | tracker); 139 | } 140 | 141 | @override 142 | TestEntityIndexed createEntity( 143 | String tString, int tInt, int tLong, double tDouble) { 144 | return TestEntityIndexed(0, tString, tInt, tLong, tDouble); 145 | } 146 | 147 | @override 148 | Future createRelBenchmark() => 149 | Future.value(ExecutorRel.create(tracker, _db)); 150 | } 151 | 152 | class ExecutorRel extends ExecutorBaseRel { 153 | final Database _db; 154 | final String _table; 155 | final String _tableTarget; 156 | final T Function(Map) _fromMap; 157 | 158 | static Future> create( 159 | TimeTracker tracker, Database db) async { 160 | final table = T.toString(); 161 | const tableTarget = 'RelTargetEntity'; 162 | 163 | await db.execute(''' 164 | CREATE TABLE $table ( 165 | id integer primary key autoincrement, 166 | relTargetId int, 167 | tString text, 168 | tLong int) 169 | '''); 170 | await db.execute('CREATE INDEX ${table}_rel ON $table(relTargetId)'); 171 | if (T == RelSourceEntityIndexed) { 172 | await db.execute('CREATE INDEX ${table}_long ON $table(tLong)'); 173 | await db.execute('CREATE INDEX ${table}_str ON $table(tString ' 174 | '${ExecutorBase.caseSensitive ? '' : 'COLLATE NOCASE'})'); 175 | } 176 | 177 | await db.execute(''' 178 | CREATE TABLE $tableTarget ( 179 | id integer primary key autoincrement, 180 | name text) 181 | '''); 182 | // Always add index on target name. 183 | await db.execute('CREATE INDEX ${tableTarget}_str ON $tableTarget(name ' 184 | '${ExecutorBase.caseSensitive ? '' : 'COLLATE NOCASE'})'); 185 | return ExecutorRel._( 186 | tracker, 187 | db, 188 | table, 189 | tableTarget, 190 | T == RelSourceEntityIndexed 191 | ? RelSourceEntityIndexed.fromMap as T Function(Map) 192 | : RelSourceEntityPlain.fromMap as T Function(Map), 193 | ); 194 | } 195 | 196 | ExecutorRel._( 197 | super.tracker, this._db, this._table, this._tableTarget, this._fromMap); 198 | 199 | @override 200 | Future close() async {} 201 | 202 | @override 203 | Future insertData(int relSourceCount, int relTargetCount) async { 204 | final targets = prepareDataTargets(relTargetCount); 205 | await _insertMany(_db, targets, RelTargetEntity.toMap); 206 | assert(targets.first.id != 0); 207 | final sources = prepareDataSources(relSourceCount, targets); 208 | await _insertMany(_db, sources, RelSourceEntity.toMap); 209 | } 210 | 211 | @override 212 | Future> queryWithLinks(List args) async => 213 | tracker.trackAsync( 214 | 'queryWithLinks', 215 | () async => _db.transaction((txn) async { 216 | late List result; 217 | final length = args.length; 218 | for (var i = 0; i < length; i++) { 219 | result = (await txn.rawQuery( 220 | 'SELECT $_table.* FROM $_table ' 221 | 'INNER JOIN $_tableTarget ON $_table.relTargetId = $_tableTarget.id ' 222 | 'WHERE $_table.tString = ? AND $_table.tLong = ? AND $_tableTarget.name = ?', 223 | [ 224 | args[i].sourceStringEquals, 225 | args[i].sourceIntEquals, 226 | args[i].targetStringEquals 227 | ])) 228 | .map(_fromMap) 229 | .toList(); 230 | } 231 | return result; 232 | })); 233 | } 234 | 235 | Future _insertMany( 236 | Database db, List items, Map Function(T) toMap) async { 237 | final tx = db.batch(); 238 | for (var object in items) { 239 | tx.insert(items.first.runtimeType.toString(), toMap(object)); 240 | } 241 | final ids = await tx.commit(); 242 | for (int i = 0; i < ids.length; i++) { 243 | items[i].id = ids[i] as int; 244 | } 245 | assert(ids.length == items.length); 246 | } 247 | -------------------------------------------------------------------------------- /lib/time_tracker.dart: -------------------------------------------------------------------------------- 1 | class TimeTracker { 2 | /// list of runtimes indexed by function name 3 | final _times = >{}; 4 | final void Function(List) outputFn; 5 | 6 | TimeTracker(this.outputFn); 7 | 8 | void clear() => _times.clear(); 9 | 10 | void _saveTime(String fnName, Stopwatch watch) { 11 | watch.stop(); 12 | 13 | _times[fnName] ??= []; 14 | _times[fnName]!.add(watch.elapsed); 15 | } 16 | 17 | // whether the function is `async` 18 | bool _isAsync(dynamic Function() fn) => fn is Future Function(); 19 | 20 | R track(String fnName, R Function() fn) { 21 | if (_isAsync(fn)) { 22 | throw UnsupportedError("Use trackAsync() to track async functions."); 23 | } 24 | 25 | final watch = Stopwatch(); 26 | 27 | watch.start(); 28 | final result = fn(); 29 | _saveTime(fnName, watch); 30 | return result; 31 | } 32 | 33 | Future trackAsync(String fnName, Future Function() fn) async { 34 | if (!_isAsync(fn)) { 35 | throw UnsupportedError("Use track() to track synchronous functions."); 36 | } 37 | 38 | final watch = Stopwatch(); 39 | 40 | watch.start(); 41 | final result = await fn(); 42 | _saveTime(fnName, watch); 43 | return result; 44 | } 45 | 46 | void _print(List varArgs) => 47 | outputFn(varArgs.map((e) => e.toString()).toList()); 48 | 49 | void printTimes({List? functions, bool avgOnly = false}) { 50 | functions ??= _times.keys.toList(); 51 | 52 | // print the data as tab-separated a table 53 | _print(avgOnly 54 | ? ['Function', 'Average ms'] 55 | : ['Function', 'Runs', 'Average ms', 'All times']); 56 | 57 | for (final fn in functions) { 58 | // Sub-millisecond values are within measurement error, 59 | // but show at least 1 decimal. 60 | final avg = averageMs(fn).toStringAsFixed(1); 61 | if (avgOnly) { 62 | _print([fn, avg]); 63 | } else { 64 | final timesCols = 65 | _times[fn]?.map((d) => d.inMicroseconds.toDouble() / 1000) ?? []; 66 | _print([fn, _count(fn), avg, ...timesCols]); 67 | } 68 | } 69 | } 70 | 71 | int _count(String fn) => _times[fn]?.length ?? 0; 72 | 73 | int _sum(String fn) => 74 | _times[fn]?.map((d) => d.inMicroseconds).reduce((v, e) => v + e) ?? 0; 75 | 76 | double averageMs(String fn) => 77 | _sum(fn).toDouble() / _count(fn).toDouble() / 1000; 78 | } 79 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(runner LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "benchapp") 5 | set(APPLICATION_ID "io.objectbox.benchapp") 6 | 7 | cmake_policy(SET CMP0063 NEW) 8 | 9 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 10 | 11 | # Root filesystem for cross-building. 12 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 13 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 14 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 15 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 16 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 17 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 18 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 19 | endif() 20 | 21 | # Configure build options. 22 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 23 | set(CMAKE_BUILD_TYPE "Debug" CACHE 24 | STRING "Flutter build mode" FORCE) 25 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 26 | "Debug" "Profile" "Release") 27 | endif() 28 | 29 | # Compilation settings that should be applied to most targets. 30 | function(APPLY_STANDARD_SETTINGS TARGET) 31 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 32 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 33 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 34 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 35 | endfunction() 36 | 37 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 38 | 39 | # Flutter library and tool build rules. 40 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 41 | 42 | # System-level dependencies. 43 | find_package(PkgConfig REQUIRED) 44 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 45 | 46 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 47 | 48 | # Application build 49 | add_executable(${BINARY_NAME} 50 | "main.cc" 51 | "my_application.cc" 52 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 53 | ) 54 | apply_standard_settings(${BINARY_NAME}) 55 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 56 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 57 | add_dependencies(${BINARY_NAME} flutter_assemble) 58 | # Only the install-generated bundle's copy of the executable will launch 59 | # correctly, since the resources must in the right relative locations. To avoid 60 | # people trying to run the unbundled copy, put it in a subdirectory instead of 61 | # the default top-level location. 62 | set_target_properties(${BINARY_NAME} 63 | PROPERTIES 64 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 65 | ) 66 | 67 | # Generated plugin build rules, which manage building the plugins and adding 68 | # them to the application. 69 | include(flutter/generated_plugins.cmake) 70 | 71 | 72 | # === Installation === 73 | # By default, "installing" just makes a relocatable bundle in the build 74 | # directory. 75 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 76 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 77 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 78 | endif() 79 | 80 | # Start with a clean build bundle directory every time. 81 | install(CODE " 82 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 83 | " COMPONENT Runtime) 84 | 85 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 86 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 87 | 88 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 89 | COMPONENT Runtime) 90 | 91 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 92 | COMPONENT Runtime) 93 | 94 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 95 | COMPONENT Runtime) 96 | 97 | if(PLUGIN_BUNDLED_LIBRARIES) 98 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 99 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 100 | COMPONENT Runtime) 101 | endif() 102 | 103 | # Fully re-copy the assets directory on each build to avoid having stale files 104 | # from a previous install. 105 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 106 | install(CODE " 107 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 108 | " COMPONENT Runtime) 109 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 110 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 111 | 112 | # Install the AOT library on non-Debug builds only. 113 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 114 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 115 | COMPONENT Runtime) 116 | endif() 117 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | 11 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 12 | # which isn't available in 3.10. 13 | function(list_prepend LIST_NAME PREFIX) 14 | set(NEW_LIST "") 15 | foreach(element ${${LIST_NAME}}) 16 | list(APPEND NEW_LIST "${PREFIX}${element}") 17 | endforeach(element) 18 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 19 | endfunction() 20 | 21 | # === Flutter Library === 22 | # System-level dependencies. 23 | find_package(PkgConfig REQUIRED) 24 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 25 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 26 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 27 | 28 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 29 | 30 | # Published to parent scope for install step. 31 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 32 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 33 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 34 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 35 | 36 | list(APPEND FLUTTER_LIBRARY_HEADERS 37 | "fl_basic_message_channel.h" 38 | "fl_binary_codec.h" 39 | "fl_binary_messenger.h" 40 | "fl_dart_project.h" 41 | "fl_engine.h" 42 | "fl_json_message_codec.h" 43 | "fl_json_method_codec.h" 44 | "fl_message_codec.h" 45 | "fl_method_call.h" 46 | "fl_method_channel.h" 47 | "fl_method_codec.h" 48 | "fl_method_response.h" 49 | "fl_plugin_registrar.h" 50 | "fl_plugin_registry.h" 51 | "fl_standard_message_codec.h" 52 | "fl_standard_method_codec.h" 53 | "fl_string_codec.h" 54 | "fl_value.h" 55 | "fl_view.h" 56 | "flutter_linux.h" 57 | ) 58 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 59 | add_library(flutter INTERFACE) 60 | target_include_directories(flutter INTERFACE 61 | "${EPHEMERAL_DIR}" 62 | ) 63 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 64 | target_link_libraries(flutter INTERFACE 65 | PkgConfig::GTK 66 | PkgConfig::GLIB 67 | PkgConfig::GIO 68 | ) 69 | add_dependencies(flutter flutter_assemble) 70 | 71 | # === Flutter tool backend === 72 | # _phony_ is a non-existent file to force this command to run every time, 73 | # since currently there's no way to get a full input/output list from the 74 | # flutter tool. 75 | add_custom_command( 76 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 77 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 78 | COMMAND ${CMAKE_COMMAND} -E env 79 | ${FLUTTER_TOOL_ENVIRONMENT} 80 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 81 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 82 | VERBATIM 83 | ) 84 | add_custom_target(flutter_assemble DEPENDS 85 | "${FLUTTER_LIBRARY}" 86 | ${FLUTTER_LIBRARY_HEADERS} 87 | ) 88 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void fl_register_plugins(FlPluginRegistry* registry) { 13 | g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar = 14 | fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin"); 15 | isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar); 16 | g_autoptr(FlPluginRegistrar) objectbox_flutter_libs_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "ObjectboxFlutterLibsPlugin"); 18 | objectbox_flutter_libs_plugin_register_with_registrar(objectbox_flutter_libs_registrar); 19 | } 20 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | isar_flutter_libs 7 | objectbox_flutter_libs 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen *screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "benchapp"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } 47 | else { 48 | gtk_window_set_title(window, "benchapp"); 49 | } 50 | 51 | gtk_window_set_default_size(window, 1280, 720); 52 | gtk_widget_show(GTK_WIDGET(window)); 53 | 54 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 55 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 56 | 57 | FlView* view = fl_view_new(project); 58 | gtk_widget_show(GTK_WIDGET(view)); 59 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 60 | 61 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 62 | 63 | gtk_widget_grab_focus(GTK_WIDGET(view)); 64 | } 65 | 66 | // Implements GApplication::local_command_line. 67 | static gboolean my_application_local_command_line(GApplication* application, gchar ***arguments, int *exit_status) { 68 | MyApplication* self = MY_APPLICATION(application); 69 | // Strip out the first argument as it is the binary name. 70 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 71 | 72 | g_autoptr(GError) error = nullptr; 73 | if (!g_application_register(application, nullptr, &error)) { 74 | g_warning("Failed to register: %s", error->message); 75 | *exit_status = 1; 76 | return TRUE; 77 | } 78 | 79 | g_application_activate(application); 80 | *exit_status = 0; 81 | 82 | return TRUE; 83 | } 84 | 85 | // Implements GObject::dispose. 86 | static void my_application_dispose(GObject *object) { 87 | MyApplication* self = MY_APPLICATION(object); 88 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 89 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 90 | } 91 | 92 | static void my_application_class_init(MyApplicationClass* klass) { 93 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 94 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 95 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 96 | } 97 | 98 | static void my_application_init(MyApplication* self) {} 99 | 100 | MyApplication* my_application_new() { 101 | return MY_APPLICATION(g_object_new(my_application_get_type(), 102 | "application-id", APPLICATION_ID, 103 | "flags", G_APPLICATION_NON_UNIQUE, 104 | nullptr)); 105 | } 106 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import isar_flutter_libs 9 | import objectbox_flutter_libs 10 | import path_provider_foundation 11 | import sqflite 12 | 13 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 14 | IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) 15 | ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin")) 16 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 17 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 18 | } 19 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - FlutterMacOS (1.0.0) 3 | - FMDB (2.7.5): 4 | - FMDB/standard (= 2.7.5) 5 | - FMDB/standard (2.7.5) 6 | - isar_flutter_libs (1.0.0): 7 | - FlutterMacOS 8 | - ObjectBox (1.8.1) 9 | - objectbox_flutter_libs (0.0.1): 10 | - FlutterMacOS 11 | - ObjectBox (= 1.8.1) 12 | - path_provider_foundation (0.0.1): 13 | - Flutter 14 | - FlutterMacOS 15 | - sqflite (0.0.2): 16 | - FlutterMacOS 17 | - FMDB (>= 2.7.5) 18 | 19 | DEPENDENCIES: 20 | - FlutterMacOS (from `Flutter/ephemeral`) 21 | - isar_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos`) 22 | - objectbox_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/objectbox_flutter_libs/macos`) 23 | - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`) 24 | - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) 25 | 26 | SPEC REPOS: 27 | trunk: 28 | - FMDB 29 | - ObjectBox 30 | 31 | EXTERNAL SOURCES: 32 | FlutterMacOS: 33 | :path: Flutter/ephemeral 34 | isar_flutter_libs: 35 | :path: Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos 36 | objectbox_flutter_libs: 37 | :path: Flutter/ephemeral/.symlinks/plugins/objectbox_flutter_libs/macos 38 | path_provider_foundation: 39 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos 40 | sqflite: 41 | :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos 42 | 43 | SPEC CHECKSUMS: 44 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 45 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 46 | isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a 47 | ObjectBox: a7900d5335218cd437cbc080b7ccc38a5211f7b4 48 | objectbox_flutter_libs: f89ab4878f0f764a49077cfaa59030be69ae1d7e 49 | path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 50 | sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea 51 | 52 | PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 53 | 54 | COCOAPODS: 1.12.0 55 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = benchapp 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = io.objectbox.benchapp 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 io.objectbox. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: benchapp 2 | description: Simple Dart DB performance benchmark 3 | version: 1.0.0+1 4 | 5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 6 | 7 | environment: 8 | sdk: '>=3.2.0 <4.0.0' 9 | flutter: '>=3.16.0' 10 | 11 | flutter: 12 | uses-material-design: true 13 | 14 | dependencies: 15 | flutter: 16 | sdk: flutter 17 | objectbox: ^4.0.0 18 | objectbox_flutter_libs: any 19 | path_provider: ^2.0.1 20 | sqflite: ^2.0.0+3 21 | path: ^1.8.0 22 | cupertino_icons: ^1.0.2 23 | hive: ^2.0.4 24 | isar: ^3.1.0 25 | isar_flutter_libs: any 26 | args: ^2.5.0 27 | 28 | dev_dependencies: 29 | objectbox_generator: any 30 | build_runner: any 31 | hive_generator: any 32 | isar_generator: any 33 | flutter_lints: ^4.0.0 34 | 35 | #dependency_overrides: 36 | # objectbox: 37 | # path: ../objectbox-dart/objectbox 38 | # objectbox_flutter_libs: 39 | # path: ../objectbox-dart/flutter_libs 40 | # objectbox_generator: 41 | # path: ../objectbox-dart/generator 42 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(benchapp LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "benchapp") 5 | 6 | cmake_policy(SET CMP0063 NEW) 7 | 8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 9 | 10 | # Configure build options. 11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 12 | if(IS_MULTICONFIG) 13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 14 | CACHE STRING "" FORCE) 15 | else() 16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 17 | set(CMAKE_BUILD_TYPE "Debug" CACHE 18 | STRING "Flutter build mode" FORCE) 19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 20 | "Debug" "Profile" "Release") 21 | endif() 22 | endif() 23 | 24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 28 | 29 | # Use Unicode for all projects. 30 | add_definitions(-DUNICODE -D_UNICODE) 31 | 32 | # Compilation settings that should be applied to most targets. 33 | function(APPLY_STANDARD_SETTINGS TARGET) 34 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 36 | target_compile_options(${TARGET} PRIVATE /EHsc) 37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 39 | endfunction() 40 | 41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 42 | 43 | # Flutter library and tool build rules. 44 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 45 | 46 | # Application build 47 | add_subdirectory("runner") 48 | 49 | # Generated plugin build rules, which manage building the plugins and adding 50 | # them to the application. 51 | include(flutter/generated_plugins.cmake) 52 | 53 | 54 | # === Installation === 55 | # Support files are copied into place next to the executable, so that it can 56 | # run in place. This is done instead of making a separate bundle (as on Linux) 57 | # so that building and running from within Visual Studio will work. 58 | set(BUILD_BUNDLE_DIR "$") 59 | # Make the "install" step default, as it's required to run. 60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 63 | endif() 64 | 65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 67 | 68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 69 | COMPONENT Runtime) 70 | 71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 72 | COMPONENT Runtime) 73 | 74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 75 | COMPONENT Runtime) 76 | 77 | if(PLUGIN_BUNDLED_LIBRARIES) 78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 80 | COMPONENT Runtime) 81 | endif() 82 | 83 | # Fully re-copy the assets directory on each build to avoid having stale files 84 | # from a previous install. 85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 86 | install(CODE " 87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 88 | " COMPONENT Runtime) 89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 91 | 92 | # Install the AOT library on non-Debug builds only. 93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 94 | CONFIGURATIONS Profile;Release 95 | COMPONENT Runtime) 96 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 11 | 12 | # === Flutter Library === 13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 14 | 15 | # Published to parent scope for install step. 16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 20 | 21 | list(APPEND FLUTTER_LIBRARY_HEADERS 22 | "flutter_export.h" 23 | "flutter_windows.h" 24 | "flutter_messenger.h" 25 | "flutter_plugin_registrar.h" 26 | "flutter_texture_registrar.h" 27 | ) 28 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 29 | add_library(flutter INTERFACE) 30 | target_include_directories(flutter INTERFACE 31 | "${EPHEMERAL_DIR}" 32 | ) 33 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 34 | add_dependencies(flutter flutter_assemble) 35 | 36 | # === Wrapper === 37 | list(APPEND CPP_WRAPPER_SOURCES_CORE 38 | "core_implementations.cc" 39 | "standard_codec.cc" 40 | ) 41 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 42 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 43 | "plugin_registrar.cc" 44 | ) 45 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 46 | list(APPEND CPP_WRAPPER_SOURCES_APP 47 | "flutter_engine.cc" 48 | "flutter_view_controller.cc" 49 | ) 50 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 51 | 52 | # Wrapper sources needed for a plugin. 53 | add_library(flutter_wrapper_plugin STATIC 54 | ${CPP_WRAPPER_SOURCES_CORE} 55 | ${CPP_WRAPPER_SOURCES_PLUGIN} 56 | ) 57 | apply_standard_settings(flutter_wrapper_plugin) 58 | set_target_properties(flutter_wrapper_plugin PROPERTIES 59 | POSITION_INDEPENDENT_CODE ON) 60 | set_target_properties(flutter_wrapper_plugin PROPERTIES 61 | CXX_VISIBILITY_PRESET hidden) 62 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 63 | target_include_directories(flutter_wrapper_plugin PUBLIC 64 | "${WRAPPER_ROOT}/include" 65 | ) 66 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 67 | 68 | # Wrapper sources needed for the runner. 69 | add_library(flutter_wrapper_app STATIC 70 | ${CPP_WRAPPER_SOURCES_CORE} 71 | ${CPP_WRAPPER_SOURCES_APP} 72 | ) 73 | apply_standard_settings(flutter_wrapper_app) 74 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 75 | target_include_directories(flutter_wrapper_app PUBLIC 76 | "${WRAPPER_ROOT}/include" 77 | ) 78 | add_dependencies(flutter_wrapper_app flutter_assemble) 79 | 80 | # === Flutter tool backend === 81 | # _phony_ is a non-existent file to force this command to run every time, 82 | # since currently there's no way to get a full input/output list from the 83 | # flutter tool. 84 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 85 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 86 | add_custom_command( 87 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 88 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 89 | ${CPP_WRAPPER_SOURCES_APP} 90 | ${PHONY_OUTPUT} 91 | COMMAND ${CMAKE_COMMAND} -E env 92 | ${FLUTTER_TOOL_ENVIRONMENT} 93 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 94 | windows-x64 $ 95 | VERBATIM 96 | ) 97 | add_custom_target(flutter_assemble DEPENDS 98 | "${FLUTTER_LIBRARY}" 99 | ${FLUTTER_LIBRARY_HEADERS} 100 | ${CPP_WRAPPER_SOURCES_CORE} 101 | ${CPP_WRAPPER_SOURCES_PLUGIN} 102 | ${CPP_WRAPPER_SOURCES_APP} 103 | ) 104 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void RegisterPlugins(flutter::PluginRegistry* registry) { 13 | IsarFlutterLibsPluginRegisterWithRegistrar( 14 | registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); 15 | ObjectboxFlutterLibsPluginRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin")); 17 | } 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | isar_flutter_libs 7 | objectbox_flutter_libs 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "run_loop.cpp" 8 | "utils.cpp" 9 | "win32_window.cpp" 10 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 11 | "Runner.rc" 12 | "runner.exe.manifest" 13 | ) 14 | apply_standard_settings(${BINARY_NAME}) 15 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 18 | add_dependencies(${BINARY_NAME} flutter_assemble) 19 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #ifdef FLUTTER_BUILD_NUMBER 64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0 67 | #endif 68 | 69 | #ifdef FLUTTER_BUILD_NAME 70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "io.objectbox" "\0" 93 | VALUE "FileDescription", "A new Flutter project." "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "benchapp" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2021 io.objectbox. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "benchapp.exe" "\0" 98 | VALUE "ProductName", "benchapp" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(RunLoop* run_loop, 8 | const flutter::DartProject& project) 9 | : run_loop_(run_loop), project_(project) {} 10 | 11 | FlutterWindow::~FlutterWindow() {} 12 | 13 | bool FlutterWindow::OnCreate() { 14 | if (!Win32Window::OnCreate()) { 15 | return false; 16 | } 17 | 18 | RECT frame = GetClientArea(); 19 | 20 | // The size here must match the window dimensions to avoid unnecessary surface 21 | // creation / destruction in the startup path. 22 | flutter_controller_ = std::make_unique( 23 | frame.right - frame.left, frame.bottom - frame.top, project_); 24 | // Ensure that basic setup of the controller was successful. 25 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 26 | return false; 27 | } 28 | RegisterPlugins(flutter_controller_->engine()); 29 | run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); 30 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 31 | return true; 32 | } 33 | 34 | void FlutterWindow::OnDestroy() { 35 | if (flutter_controller_) { 36 | run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); 37 | flutter_controller_ = nullptr; 38 | } 39 | 40 | Win32Window::OnDestroy(); 41 | } 42 | 43 | LRESULT 44 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 45 | WPARAM const wparam, 46 | LPARAM const lparam) noexcept { 47 | // Give Flutter, including plugins, an opportunity to handle window messages. 48 | if (flutter_controller_) { 49 | std::optional result = 50 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 51 | lparam); 52 | if (result) { 53 | return *result; 54 | } 55 | } 56 | 57 | switch (message) { 58 | case WM_FONTCHANGE: 59 | flutter_controller_->engine()->ReloadSystemFonts(); 60 | break; 61 | } 62 | 63 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "run_loop.h" 10 | #include "win32_window.h" 11 | 12 | // A window that does nothing but host a Flutter view. 13 | class FlutterWindow : public Win32Window { 14 | public: 15 | // Creates a new FlutterWindow driven by the |run_loop|, hosting a 16 | // Flutter view running |project|. 17 | explicit FlutterWindow(RunLoop* run_loop, 18 | const flutter::DartProject& project); 19 | virtual ~FlutterWindow(); 20 | 21 | protected: 22 | // Win32Window: 23 | bool OnCreate() override; 24 | void OnDestroy() override; 25 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 26 | LPARAM const lparam) noexcept override; 27 | 28 | private: 29 | // The run loop driving events for this window. 30 | RunLoop* run_loop_; 31 | 32 | // The project to run. 33 | flutter::DartProject project_; 34 | 35 | // The Flutter instance hosted by this window. 36 | std::unique_ptr flutter_controller_; 37 | }; 38 | 39 | #endif // RUNNER_FLUTTER_WINDOW_H_ 40 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "run_loop.h" 7 | #include "utils.h" 8 | 9 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 10 | _In_ wchar_t *command_line, _In_ int show_command) { 11 | // Attach to console when present (e.g., 'flutter run') or create a 12 | // new console when running with a debugger. 13 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 14 | CreateAndAttachConsole(); 15 | } 16 | 17 | // Initialize COM, so that it is available for use in the library and/or 18 | // plugins. 19 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 20 | 21 | RunLoop run_loop; 22 | 23 | flutter::DartProject project(L"data"); 24 | 25 | std::vector command_line_arguments = 26 | GetCommandLineArguments(); 27 | 28 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 29 | 30 | FlutterWindow window(&run_loop, project); 31 | Win32Window::Point origin(10, 10); 32 | Win32Window::Size size(1280, 720); 33 | if (!window.CreateAndShow(L"benchapp", origin, size)) { 34 | return EXIT_FAILURE; 35 | } 36 | window.SetQuitOnClose(true); 37 | 38 | run_loop.Run(); 39 | 40 | ::CoUninitialize(); 41 | return EXIT_SUCCESS; 42 | } 43 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/objectbox/objectbox-dart-performance/9844dbb77d9b47930683821f450ffcea8c5b5725/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/run_loop.cpp: -------------------------------------------------------------------------------- 1 | #include "run_loop.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | RunLoop::RunLoop() {} 8 | 9 | RunLoop::~RunLoop() {} 10 | 11 | void RunLoop::Run() { 12 | bool keep_running = true; 13 | TimePoint next_flutter_event_time = TimePoint::clock::now(); 14 | while (keep_running) { 15 | std::chrono::nanoseconds wait_duration = 16 | std::max(std::chrono::nanoseconds(0), 17 | next_flutter_event_time - TimePoint::clock::now()); 18 | ::MsgWaitForMultipleObjects( 19 | 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), 20 | QS_ALLINPUT); 21 | bool processed_events = false; 22 | MSG message; 23 | // All pending Windows messages must be processed; MsgWaitForMultipleObjects 24 | // won't return again for items left in the queue after PeekMessage. 25 | while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { 26 | processed_events = true; 27 | if (message.message == WM_QUIT) { 28 | keep_running = false; 29 | break; 30 | } 31 | ::TranslateMessage(&message); 32 | ::DispatchMessage(&message); 33 | // Allow Flutter to process messages each time a Windows message is 34 | // processed, to prevent starvation. 35 | next_flutter_event_time = 36 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 37 | } 38 | // If the PeekMessage loop didn't run, process Flutter messages. 39 | if (!processed_events) { 40 | next_flutter_event_time = 41 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 42 | } 43 | } 44 | } 45 | 46 | void RunLoop::RegisterFlutterInstance( 47 | flutter::FlutterEngine* flutter_instance) { 48 | flutter_instances_.insert(flutter_instance); 49 | } 50 | 51 | void RunLoop::UnregisterFlutterInstance( 52 | flutter::FlutterEngine* flutter_instance) { 53 | flutter_instances_.erase(flutter_instance); 54 | } 55 | 56 | RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { 57 | TimePoint next_event_time = TimePoint::max(); 58 | for (auto instance : flutter_instances_) { 59 | std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); 60 | if (wait_duration != std::chrono::nanoseconds::max()) { 61 | next_event_time = 62 | std::min(next_event_time, TimePoint::clock::now() + wait_duration); 63 | } 64 | } 65 | return next_event_time; 66 | } 67 | -------------------------------------------------------------------------------- /windows/runner/run_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_RUN_LOOP_H_ 2 | #define RUNNER_RUN_LOOP_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // A runloop that will service events for Flutter instances as well 10 | // as native messages. 11 | class RunLoop { 12 | public: 13 | RunLoop(); 14 | ~RunLoop(); 15 | 16 | // Prevent copying 17 | RunLoop(RunLoop const&) = delete; 18 | RunLoop& operator=(RunLoop const&) = delete; 19 | 20 | // Runs the run loop until the application quits. 21 | void Run(); 22 | 23 | // Registers the given Flutter instance for event servicing. 24 | void RegisterFlutterInstance( 25 | flutter::FlutterEngine* flutter_instance); 26 | 27 | // Unregisters the given Flutter instance from event servicing. 28 | void UnregisterFlutterInstance( 29 | flutter::FlutterEngine* flutter_instance); 30 | 31 | private: 32 | using TimePoint = std::chrono::steady_clock::time_point; 33 | 34 | // Processes all currently pending messages for registered Flutter instances. 35 | TimePoint ProcessFlutterMessages(); 36 | 37 | std::set flutter_instances_; 38 | }; 39 | 40 | #endif // RUNNER_RUN_LOOP_H_ 41 | -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | 5 | #include "resource.h" 6 | 7 | namespace { 8 | 9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 10 | 11 | // The number of Win32Window objects that currently exist. 12 | static int g_active_window_count = 0; 13 | 14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 15 | 16 | // Scale helper to convert logical scaler values to physical using passed in 17 | // scale factor 18 | int Scale(int source, double scale_factor) { 19 | return static_cast(source * scale_factor); 20 | } 21 | 22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 23 | // This API is only needed for PerMonitor V1 awareness mode. 24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 25 | HMODULE user32_module = LoadLibraryA("User32.dll"); 26 | if (!user32_module) { 27 | return; 28 | } 29 | auto enable_non_client_dpi_scaling = 30 | reinterpret_cast( 31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 32 | if (enable_non_client_dpi_scaling != nullptr) { 33 | enable_non_client_dpi_scaling(hwnd); 34 | FreeLibrary(user32_module); 35 | } 36 | } 37 | 38 | } // namespace 39 | 40 | // Manages the Win32Window's window class registration. 41 | class WindowClassRegistrar { 42 | public: 43 | ~WindowClassRegistrar() = default; 44 | 45 | // Returns the singleton registar instance. 46 | static WindowClassRegistrar* GetInstance() { 47 | if (!instance_) { 48 | instance_ = new WindowClassRegistrar(); 49 | } 50 | return instance_; 51 | } 52 | 53 | // Returns the name of the window class, registering the class if it hasn't 54 | // previously been registered. 55 | const wchar_t* GetWindowClass(); 56 | 57 | // Unregisters the window class. Should only be called if there are no 58 | // instances of the window. 59 | void UnregisterWindowClass(); 60 | 61 | private: 62 | WindowClassRegistrar() = default; 63 | 64 | static WindowClassRegistrar* instance_; 65 | 66 | bool class_registered_ = false; 67 | }; 68 | 69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 70 | 71 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 72 | if (!class_registered_) { 73 | WNDCLASS window_class{}; 74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 75 | window_class.lpszClassName = kWindowClassName; 76 | window_class.style = CS_HREDRAW | CS_VREDRAW; 77 | window_class.cbClsExtra = 0; 78 | window_class.cbWndExtra = 0; 79 | window_class.hInstance = GetModuleHandle(nullptr); 80 | window_class.hIcon = 81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 82 | window_class.hbrBackground = 0; 83 | window_class.lpszMenuName = nullptr; 84 | window_class.lpfnWndProc = Win32Window::WndProc; 85 | RegisterClass(&window_class); 86 | class_registered_ = true; 87 | } 88 | return kWindowClassName; 89 | } 90 | 91 | void WindowClassRegistrar::UnregisterWindowClass() { 92 | UnregisterClass(kWindowClassName, nullptr); 93 | class_registered_ = false; 94 | } 95 | 96 | Win32Window::Win32Window() { 97 | ++g_active_window_count; 98 | } 99 | 100 | Win32Window::~Win32Window() { 101 | --g_active_window_count; 102 | Destroy(); 103 | } 104 | 105 | bool Win32Window::CreateAndShow(const std::wstring& title, 106 | const Point& origin, 107 | const Size& size) { 108 | Destroy(); 109 | 110 | const wchar_t* window_class = 111 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 112 | 113 | const POINT target_point = {static_cast(origin.x), 114 | static_cast(origin.y)}; 115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 117 | double scale_factor = dpi / 96.0; 118 | 119 | HWND window = CreateWindow( 120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 123 | nullptr, nullptr, GetModuleHandle(nullptr), this); 124 | 125 | if (!window) { 126 | return false; 127 | } 128 | 129 | return OnCreate(); 130 | } 131 | 132 | // static 133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 134 | UINT const message, 135 | WPARAM const wparam, 136 | LPARAM const lparam) noexcept { 137 | if (message == WM_NCCREATE) { 138 | auto window_struct = reinterpret_cast(lparam); 139 | SetWindowLongPtr(window, GWLP_USERDATA, 140 | reinterpret_cast(window_struct->lpCreateParams)); 141 | 142 | auto that = static_cast(window_struct->lpCreateParams); 143 | EnableFullDpiSupportIfAvailable(window); 144 | that->window_handle_ = window; 145 | } else if (Win32Window* that = GetThisFromHandle(window)) { 146 | return that->MessageHandler(window, message, wparam, lparam); 147 | } 148 | 149 | return DefWindowProc(window, message, wparam, lparam); 150 | } 151 | 152 | LRESULT 153 | Win32Window::MessageHandler(HWND hwnd, 154 | UINT const message, 155 | WPARAM const wparam, 156 | LPARAM const lparam) noexcept { 157 | switch (message) { 158 | case WM_DESTROY: 159 | window_handle_ = nullptr; 160 | Destroy(); 161 | if (quit_on_close_) { 162 | PostQuitMessage(0); 163 | } 164 | return 0; 165 | 166 | case WM_DPICHANGED: { 167 | auto newRectSize = reinterpret_cast(lparam); 168 | LONG newWidth = newRectSize->right - newRectSize->left; 169 | LONG newHeight = newRectSize->bottom - newRectSize->top; 170 | 171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 173 | 174 | return 0; 175 | } 176 | case WM_SIZE: { 177 | RECT rect = GetClientArea(); 178 | if (child_content_ != nullptr) { 179 | // Size and position the child window. 180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 181 | rect.bottom - rect.top, TRUE); 182 | } 183 | return 0; 184 | } 185 | 186 | case WM_ACTIVATE: 187 | if (child_content_ != nullptr) { 188 | SetFocus(child_content_); 189 | } 190 | return 0; 191 | } 192 | 193 | return DefWindowProc(window_handle_, message, wparam, lparam); 194 | } 195 | 196 | void Win32Window::Destroy() { 197 | OnDestroy(); 198 | 199 | if (window_handle_) { 200 | DestroyWindow(window_handle_); 201 | window_handle_ = nullptr; 202 | } 203 | if (g_active_window_count == 0) { 204 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 205 | } 206 | } 207 | 208 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 209 | return reinterpret_cast( 210 | GetWindowLongPtr(window, GWLP_USERDATA)); 211 | } 212 | 213 | void Win32Window::SetChildContent(HWND content) { 214 | child_content_ = content; 215 | SetParent(content, window_handle_); 216 | RECT frame = GetClientArea(); 217 | 218 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 219 | frame.bottom - frame.top, true); 220 | 221 | SetFocus(child_content_); 222 | } 223 | 224 | RECT Win32Window::GetClientArea() { 225 | RECT frame; 226 | GetClientRect(window_handle_, &frame); 227 | return frame; 228 | } 229 | 230 | HWND Win32Window::GetHandle() { 231 | return window_handle_; 232 | } 233 | 234 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 235 | quit_on_close_ = quit_on_close; 236 | } 237 | 238 | bool Win32Window::OnCreate() { 239 | // No-op; provided for subclasses. 240 | return true; 241 | } 242 | 243 | void Win32Window::OnDestroy() { 244 | // No-op; provided for subclasses. 245 | } 246 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | --------------------------------------------------------------------------------