mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-24 13:47:24 +00:00
Compare commits
8 Commits
cherrypick
...
cherrypick
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
009808975e | ||
|
|
e3eb0eef06 | ||
|
|
32bda69476 | ||
|
|
d24d6e3f2b | ||
|
|
1f08231402 | ||
|
|
09de8bca98 | ||
|
|
b3660eee93 | ||
|
|
62c8b2247b |
38
.github/workflows/pipeline.yml
vendored
38
.github/workflows/pipeline.yml
vendored
@@ -1,38 +0,0 @@
|
|||||||
name: Melos + FVM CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "master" ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: dart-lang/setup-dart@v1
|
|
||||||
# также актуализация Flutter, если нужен fvm
|
|
||||||
|
|
||||||
- name: Install FVM
|
|
||||||
run: dart pub global activate fvm
|
|
||||||
|
|
||||||
- name: Install Flutter version via FVM
|
|
||||||
run: fvm install
|
|
||||||
|
|
||||||
# ВАЖНО: активируем melos через flutter, чтобы не было несовместимости
|
|
||||||
- name: Install Melos
|
|
||||||
run: fvm flutter pub global activate melos
|
|
||||||
|
|
||||||
- name: Bootstrap workspace
|
|
||||||
run: fvm flutter pub global run melos bootstrap
|
|
||||||
|
|
||||||
- name: CodeGen
|
|
||||||
run: fvm flutter pub global run melos run codegen
|
|
||||||
|
|
||||||
- name: Analyze all packages
|
|
||||||
run: fvm flutter pub global run melos run analyze
|
|
||||||
|
|
||||||
- name: Run all tests
|
|
||||||
run: fvm flutter pub global run melos run test
|
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -7,16 +7,8 @@
|
|||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
|
||||||
**/*.g.dart
|
|
||||||
**/*.gr.dart
|
|
||||||
**/*.freezed.dart
|
|
||||||
**/*.cherrypick_injectable.g.dart
|
|
||||||
|
|
||||||
pubspec_overrides.yaml
|
pubspec_overrides.yaml
|
||||||
|
|
||||||
melos_cherrypick.iml
|
melos_cherrypick.iml
|
||||||
melos_cherrypick_workspace.iml
|
melos_cherrypick_workspace.iml
|
||||||
melos_cherrypick_flutter.iml
|
melos_cherrypick_flutter.iml
|
||||||
|
|
||||||
coverage
|
|
||||||
487
CHANGELOG.md
487
CHANGELOG.md
@@ -3,493 +3,6 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
## 2025-08-11
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v3.0.0-dev.7`](#cherrypick---v300-dev7)
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick_annotations` - `v1.1.1`](#cherrypick_annotations---v111)
|
|
||||||
- [`cherrypick_flutter` - `v1.1.3-dev.7`](#cherrypick_flutter---v113-dev7)
|
|
||||||
- [`cherrypick_generator` - `v1.1.1`](#cherrypick_generator---v111)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v3.0.0-dev.7`
|
|
||||||
|
|
||||||
- **FIX**(comment): fix warnings.
|
|
||||||
- **FIX**(license): correct urls.
|
|
||||||
- **FEAT**: add Disposable interface source and usage example.
|
|
||||||
- **DOCS**(readme): add comprehensive section on annotations and DI code generation.
|
|
||||||
- **DOCS**(readme): add detailed section and examples for automatic Disposable resource cleanup\n\n- Added a dedicated section with English description and code samples on using Disposable for automatic resource management.\n- Updated Features to include automatic resource cleanup for Disposable dependencies.\n\nHelps developers understand and implement robust DI resource management practices.
|
|
||||||
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
|
||||||
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
|
||||||
- **BREAKING** **REFACTOR**(core): make closeRootScope async and await dispose.
|
|
||||||
- **BREAKING** **DOCS**(disposable): add detailed English documentation and usage examples for Disposable interface; chore: update binding_resolver and add explanatory comment in scope_test for deprecated usage.\n\n- Expanded Disposable interface docs, added sync & async example classes, and CherryPick integration sample.\n- Clarified how to implement and use Disposable in DI context.\n- Updated binding_resolver for internal improvements.\n- Added ignore for deprecated member use in scope_test for clarity and future upgrades.\n\nBREAKING CHANGE: Documentation style enhancement and clearer API usage for Disposable implementations.
|
|
||||||
|
|
||||||
#### `cherrypick_annotations` - `v1.1.1`
|
|
||||||
|
|
||||||
- **FIX**(license): correct urls.
|
|
||||||
|
|
||||||
#### `cherrypick_flutter` - `v1.1.3-dev.7`
|
|
||||||
|
|
||||||
- **FIX**(license): correct urls.
|
|
||||||
|
|
||||||
#### `cherrypick_generator` - `v1.1.1`
|
|
||||||
|
|
||||||
- **FIX**(license): correct urls.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-08-08
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v3.0.0-dev.6`](#cherrypick---v300-dev6)
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick_flutter` - `v1.1.3-dev.6`](#cherrypick_flutter---v113-dev6)
|
|
||||||
|
|
||||||
Packages with dependency updates only:
|
|
||||||
|
|
||||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
|
||||||
|
|
||||||
- `cherrypick_flutter` - `v1.1.3-dev.6`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v3.0.0-dev.6`
|
|
||||||
|
|
||||||
- **FIX**: improve global cycle detector logic.
|
|
||||||
- **DOCS**(readme): add comprehensive DI state and action logging to features.
|
|
||||||
- **DOCS**(helper): add complete DartDoc with real usage examples for CherryPick class.
|
|
||||||
- **DOCS**(log_format): add detailed English documentation for formatLogMessage function.
|
|
||||||
- **BREAKING** **FEAT**(core): refactor root scope API, improve logger injection, helpers, and tests.
|
|
||||||
- **BREAKING** **FEAT**(logger): add extensible logging API, usage examples, and bilingual documentation.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-08-07
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v3.0.0-dev.5`](#cherrypick---v300-dev5)
|
|
||||||
- [`cherrypick_flutter` - `v1.1.3-dev.5`](#cherrypick_flutter---v113-dev5)
|
|
||||||
|
|
||||||
Packages with dependency updates only:
|
|
||||||
|
|
||||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
|
||||||
|
|
||||||
- `cherrypick_flutter` - `v1.1.3-dev.5`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v3.0.0-dev.5`
|
|
||||||
|
|
||||||
- **REFACTOR**(scope): simplify _findBindingResolver<T> with one-liner and optional chaining.
|
|
||||||
- **PERF**(scope): speed up dependency lookup with Map-based binding resolver index.
|
|
||||||
- **DOCS**(perf): clarify Map-based resolver optimization applies since v3.0.0 in all docs.
|
|
||||||
- **DOCS**: update EN/RU quick start and tutorial with Fast Map-based lookup section; clarify performance benefit in README.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-08-07
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v3.0.0-dev.4`](#cherrypick---v300-dev4)
|
|
||||||
- [`cherrypick_flutter` - `v1.1.3-dev.4`](#cherrypick_flutter---v113-dev4)
|
|
||||||
|
|
||||||
Packages with dependency updates only:
|
|
||||||
|
|
||||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
|
||||||
|
|
||||||
- `cherrypick_flutter` - `v1.1.3-dev.4`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v3.0.0-dev.4`
|
|
||||||
|
|
||||||
- **REFACTOR**(scope): simplify _findBindingResolver<T> with one-liner and optional chaining.
|
|
||||||
- **PERF**(scope): speed up dependency lookup with Map-based binding resolver index.
|
|
||||||
- **DOCS**(perf): clarify Map-based resolver optimization applies since v3.0.0 in all docs.
|
|
||||||
- **DOCS**: update EN/RU quick start and tutorial with Fast Map-based lookup section; clarify performance benefit in README.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-08-07
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v3.0.0-dev.3`](#cherrypick---v300-dev3)
|
|
||||||
- [`cherrypick_flutter` - `v1.1.3-dev.3`](#cherrypick_flutter---v113-dev3)
|
|
||||||
|
|
||||||
Packages with dependency updates only:
|
|
||||||
|
|
||||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
|
||||||
|
|
||||||
- `cherrypick_flutter` - `v1.1.3-dev.3`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v3.0.0-dev.3`
|
|
||||||
|
|
||||||
- **REFACTOR**(scope): simplify _findBindingResolver<T> with one-liner and optional chaining.
|
|
||||||
- **PERF**(scope): speed up dependency lookup with Map-based binding resolver index.
|
|
||||||
- **DOCS**(perf): clarify Map-based resolver optimization applies since v3.0.0 in all docs.
|
|
||||||
- **DOCS**: update EN/RU quick start and tutorial with Fast Map-based lookup section; clarify performance benefit in README.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-08-04
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v3.0.0-dev.2`](#cherrypick---v300-dev2)
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick_flutter` - `v1.1.3-dev.2`](#cherrypick_flutter---v113-dev2)
|
|
||||||
|
|
||||||
Packages with dependency updates only:
|
|
||||||
|
|
||||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
|
||||||
|
|
||||||
- `cherrypick_flutter` - `v1.1.3-dev.2`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v3.0.0-dev.2`
|
|
||||||
|
|
||||||
- **FEAT**(binding): add deprecated proxy async methods for backward compatibility and highlight transition to modern API.
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
- **BREAKING** **FEAT**: implement comprehensive circular dependency detection system.
|
|
||||||
- **BREAKING** **FEAT**: implement comprehensive circular dependency detection system.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-07-30
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v3.0.0-dev.1`](#cherrypick---v300-dev1)
|
|
||||||
- [`cherrypick_flutter` - `v1.1.3-dev.1`](#cherrypick_flutter---v113-dev1)
|
|
||||||
|
|
||||||
Packages with dependency updates only:
|
|
||||||
|
|
||||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
|
||||||
|
|
||||||
- `cherrypick_flutter` - `v1.1.3-dev.1`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v3.0.0-dev.1`
|
|
||||||
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-07-30
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v3.0.0-dev.0`](#cherrypick---v300-dev0)
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick_flutter` - `v1.1.3-dev.0`](#cherrypick_flutter---v113-dev0)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v3.0.0-dev.0`
|
|
||||||
|
|
||||||
- **BREAKING** **FEAT**: implement comprehensive circular dependency detection system.
|
|
||||||
|
|
||||||
#### `cherrypick_flutter` - `v1.1.3-dev.0`
|
|
||||||
|
|
||||||
- **FIX**: update deps.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-07-28
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- [`cherrypick_flutter` - `v1.1.2`](#cherrypick_flutter---v112)
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v2.2.0`](#cherrypick---v220)
|
|
||||||
- [`cherrypick_annotations` - `v1.1.0`](#cherrypick_annotations---v110)
|
|
||||||
- [`cherrypick_generator` - `v1.1.0`](#cherrypick_generator---v110)
|
|
||||||
|
|
||||||
Packages graduated to a stable release (see pre-releases prior to the stable version for changelog entries):
|
|
||||||
|
|
||||||
- `cherrypick` - `v2.2.0`
|
|
||||||
- `cherrypick_annotations` - `v1.1.0`
|
|
||||||
- `cherrypick_flutter` - `v1.1.2`
|
|
||||||
- `cherrypick_generator` - `v1.1.0`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick_flutter` - `v1.1.2`
|
|
||||||
|
|
||||||
#### `cherrypick` - `v2.2.0`
|
|
||||||
|
|
||||||
#### `cherrypick_annotations` - `v1.1.0`
|
|
||||||
|
|
||||||
#### `cherrypick_generator` - `v1.1.0`
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-06-04
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick_generator` - `v1.1.0-dev.5`](#cherrypick_generator---v110-dev5)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick_generator` - `v1.1.0-dev.5`
|
|
||||||
|
|
||||||
- **FEAT**: implement tryResolve via generate code.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-05-28
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick_generator` - `v1.1.0-dev.4`](#cherrypick_generator---v110-dev4)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick_generator` - `v1.1.0-dev.4`
|
|
||||||
|
|
||||||
- **FIX**: fixed warnings.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-05-23
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick_annotations` - `v1.1.0-dev.1`](#cherrypick_annotations---v110-dev1)
|
|
||||||
- [`cherrypick_generator` - `v1.1.0-dev.3`](#cherrypick_generator---v110-dev3)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick_annotations` - `v1.1.0-dev.1`
|
|
||||||
|
|
||||||
- **FEAT**: implement InjectGenerator.
|
|
||||||
|
|
||||||
#### `cherrypick_generator` - `v1.1.0-dev.3`
|
|
||||||
|
|
||||||
- **FEAT**: implement InjectGenerator.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-05-23
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick_generator` - `v1.1.0-dev.2`](#cherrypick_generator---v110-dev2)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick_generator` - `v1.1.0-dev.2`
|
|
||||||
|
|
||||||
- **FIX**: update instance generator code.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-05-22
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v2.2.0-dev.1`](#cherrypick---v220-dev1)
|
|
||||||
- [`cherrypick_generator` - `v1.1.0-dev.1`](#cherrypick_generator---v110-dev1)
|
|
||||||
- [`cherrypick_flutter` - `v1.1.2-dev.1`](#cherrypick_flutter---v112-dev1)
|
|
||||||
|
|
||||||
Packages with dependency updates only:
|
|
||||||
|
|
||||||
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
|
||||||
|
|
||||||
- `cherrypick_flutter` - `v1.1.2-dev.1`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v2.2.0-dev.1`
|
|
||||||
|
|
||||||
- **FIX**: fix warnings.
|
|
||||||
|
|
||||||
#### `cherrypick_generator` - `v1.1.0-dev.1`
|
|
||||||
|
|
||||||
- **FIX**: optimize code.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-05-22
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- There are no breaking changes in this release.
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v2.2.0-dev.0`](#cherrypick---v220-dev0)
|
|
||||||
- [`cherrypick_annotations` - `v1.1.0-dev.0`](#cherrypick_annotations---v110-dev0)
|
|
||||||
- [`cherrypick_flutter` - `v1.1.2-dev.0`](#cherrypick_flutter---v112-dev0)
|
|
||||||
- [`cherrypick_generator` - `v1.1.0-dev.0`](#cherrypick_generator---v110-dev0)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick` - `v2.2.0-dev.0`
|
|
||||||
|
|
||||||
- **FEAT**: Add async dependency resolution and enhance example.
|
|
||||||
- **FEAT**: implement toInstanceAync binding.
|
|
||||||
|
|
||||||
#### `cherrypick_annotations` - `v1.1.0-dev.0`
|
|
||||||
|
|
||||||
- **FEAT**: implement generator for dynamic params.
|
|
||||||
- **FEAT**: implement instance/provide annotations.
|
|
||||||
- **FEAT**: implement generator for named annotation.
|
|
||||||
- **FEAT**: implement generator di module.
|
|
||||||
- **FEAT**: implement annotations.
|
|
||||||
|
|
||||||
#### `cherrypick_flutter` - `v1.1.2-dev.0`
|
|
||||||
|
|
||||||
- **FIX**: fix warning.
|
|
||||||
- **FIX**: fix warnings.
|
|
||||||
|
|
||||||
#### `cherrypick_generator` - `v1.1.0-dev.0`
|
|
||||||
|
|
||||||
- **FIX**: fix warning conflict with names.
|
|
||||||
- **FIX**: fix warnings.
|
|
||||||
- **FIX**: fix module generator.
|
|
||||||
- **FIX**: fix generator for singletone annotation.
|
|
||||||
- **FEAT**: implement generator for dynamic params.
|
|
||||||
- **FEAT**: implement async mode for instance/provide annotations.
|
|
||||||
- **FEAT**: generate instance async code.
|
|
||||||
- **FEAT**: implement instance/provide annotations.
|
|
||||||
- **FEAT**: implement named dependency.
|
|
||||||
- **FEAT**: implement generator for named annotation.
|
|
||||||
- **FEAT**: implement generator di module.
|
|
||||||
- **FEAT**: implement annotations.
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-05-19
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Packages with breaking changes:
|
|
||||||
|
|
||||||
- [`cherrypick_flutter` - `v1.1.1`](#cherrypick_flutter---v111)
|
|
||||||
|
|
||||||
Packages with other changes:
|
|
||||||
|
|
||||||
- [`cherrypick` - `v2.1.0`](#cherrypick---v210)
|
|
||||||
|
|
||||||
Packages graduated to a stable release (see pre-releases prior to the stable version for changelog entries):
|
|
||||||
|
|
||||||
- `cherrypick` - `v2.1.0`
|
|
||||||
- `cherrypick_flutter` - `v1.1.1`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### `cherrypick_flutter` - `v1.1.1`
|
|
||||||
|
|
||||||
#### `cherrypick` - `v2.1.0`
|
|
||||||
|
|
||||||
|
|
||||||
## 2025-05-16
|
## 2025-05-16
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
# Contributors
|
|
||||||
|
|
||||||
- Sergey Penkovsky <sergey.penkovsky@gmail.com>
|
|
||||||
- Klim Yaroshevich <yarashevich_kv@st.by>
|
|
||||||
2
LICENSE
2
LICENSE
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
https://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
159
README.md
159
README.md
@@ -1,145 +1,100 @@
|
|||||||
# CherryPick Workspace
|
# CherryPick Workspace
|
||||||
|
|
||||||
CherryPick Workspace is a modular, open-source dependency injection ecosystem for Dart and Flutter, designed to offer lightweight, flexible, and scalable DI suitable for both backend and frontend (Flutter) development. This monorepo contains the main DI runtime library, annotation helpers, code generation for modular bindings, and seamless Flutter integration.
|
Welcome to the CherryPick Workspace, a comprehensive suite for dependency management in Flutter applications. It consists of the `cherrypick` and `cherrypick_flutter` packages, designed to enhance modularity and testability by providing robust dependency and state management tools.
|
||||||
|
|
||||||
---
|
## Overview
|
||||||
|
|
||||||
## Packages Overview
|
- **`cherrypick`**: A Dart library offering core tools for dependency injection and management through modules and scopes.
|
||||||
|
- **`cherrypick_flutter`**: A Flutter-specific library facilitating access to the root scope via the context using `CherryPickProvider`, simplifying state management within the widget tree.
|
||||||
|
|
||||||
- **[`cherrypick`](./cherrypick)**
|
## Repository Structure
|
||||||
The core dependency injection library. Supports modular bindings, hierarchical scopes, named and singleton bindings, provider functions (sync/async), runtime parameters, and test-friendly composition.
|
|
||||||
_Intended for use in pure Dart and Flutter projects._
|
|
||||||
|
|
||||||
- **[`cherrypick_annotations`](./cherrypick_annotations)**
|
- **Packages**:
|
||||||
A set of Dart annotations (`@module`, `@singleton`, `@instance`, `@provide`, `@named`, `@params`) enabling concise, declarative DI modules and providers, primarily for use with code generation tools.
|
- `cherrypick`: Core DI functionalities.
|
||||||
|
- `cherrypick_flutter`: Flutter integration for context-based root scope access.
|
||||||
|
|
||||||
- **[`cherrypick_generator`](./cherrypick_generator)**
|
## Quick Start Guide
|
||||||
A [source_gen](https://pub.dev/packages/source_gen)-based code generator that automatically converts your annotated modules and providers into ready-to-use boilerplate for registration and resolution within your app.
|
|
||||||
_Reduces manual wiring and errors; compatible with build_runner._
|
|
||||||
|
|
||||||
- **[`cherrypick_flutter`](./cherrypick_flutter)**
|
### Installation
|
||||||
Adds Flutter-native integration, exposing DI scopes and modules to the widget tree through `CherryPickProvider` and enabling dependency management throughout your Flutter app.
|
|
||||||
|
|
||||||
---
|
To add the packages to your project, include the dependencies in your `pubspec.yaml`:
|
||||||
|
|
||||||
## Why CherryPick?
|
|
||||||
|
|
||||||
- **Zero-overhead and intuitive API:**
|
|
||||||
Clean, minimal syntax, strong typing, powerful binding lifecycle control.
|
|
||||||
- **High testability:**
|
|
||||||
Supports overriding and hierarchical scope trees.
|
|
||||||
- **Both Sync & Async support:**
|
|
||||||
Register and resolve async providers, factories, and dependencies.
|
|
||||||
- **Seamless code generation:**
|
|
||||||
Effortless setup with annotations + generator—skip boilerplate!
|
|
||||||
- **Works with or without Flutter.**
|
|
||||||
- **Production ready:**
|
|
||||||
Robust enough for apps, packages, and server-side Dart.
|
|
||||||
- **Extensible & Modular:**
|
|
||||||
Add bindings at runtime, use sub-modules, or integrate via codegen.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Get Started
|
|
||||||
|
|
||||||
### 1. Add dependencies
|
|
||||||
|
|
||||||
In your `pubspec.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
dependencies:
|
dependencies:
|
||||||
cherrypick: ^<latest-version>
|
cherrypick: any
|
||||||
cherrypick_annotations: ^<latest-version>
|
cherrypick_flutter: any
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
build_runner: ^<latest>
|
|
||||||
cherrypick_generator: ^<latest-version>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
For Flutter projects, add:
|
Run `flutter pub get` to install the dependencies.
|
||||||
|
|
||||||
```yaml
|
### Usage
|
||||||
dependencies:
|
|
||||||
cherrypick_flutter: ^<latest-version>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Write a DI Module (with annotations)
|
#### cherrypick
|
||||||
|
|
||||||
|
- **Binding Dependencies**: Use `Binding` to set up dependencies.
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
Binding<String>().toInstance("hello world");
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
Binding<String>().toProvide(() => "hello world").singleton();
|
||||||
|
```
|
||||||
|
|
||||||
@module()
|
- **Creating Modules**: Define dependencies within a module.
|
||||||
abstract class MyModule extends Module {
|
|
||||||
@singleton()
|
|
||||||
ApiClient apiClient() => ApiClient();
|
|
||||||
|
|
||||||
@provide()
|
```dart
|
||||||
DataRepository dataRepo(ApiClient client) => DataRepository(client);
|
class AppModule extends Module {
|
||||||
|
@override
|
||||||
@provide()
|
void builder(Scope currentScope) {
|
||||||
String greeting(@params() String name) => 'Hello, $name!';
|
bind<ApiClient>().toInstance(ApiClientMock());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Generate the bindings
|
- **Managing Scopes**: Control dependency lifecycles with scopes.
|
||||||
|
|
||||||
```sh
|
|
||||||
dart run build_runner build
|
|
||||||
# or for Flutter:
|
|
||||||
flutter pub run build_runner build
|
|
||||||
```
|
|
||||||
|
|
||||||
The generator will create a `$MyModule` class with binding code.
|
|
||||||
|
|
||||||
### 4. Install and Resolve
|
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
final scope = CherryPick.openRootScope()
|
final rootScope = Cherrypick.openRootScope();
|
||||||
..installModules([$MyModule()]);
|
rootScope.installModules([AppModule()]);
|
||||||
|
final apiClient = rootScope.resolve<ApiClient>();
|
||||||
final repo = scope.resolve<DataRepository>();
|
|
||||||
final greeting = scope.resolve<String>(params: 'John'); // 'Hello, John!'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
_For Flutter, wrap your app with `CherryPickProvider` for DI scopes in the widget tree:_
|
#### cherrypick_flutter
|
||||||
|
|
||||||
|
- **CherryPickProvider**: Wrap your widget tree to access the root scope via context.
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
void main() {
|
void main() {
|
||||||
runApp(
|
runApp(CherryPickProvider(
|
||||||
CherryPickProvider(child: MyApp()),
|
rootScope: yourRootScopeInstance,
|
||||||
);
|
child: MyApp(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
- **Accessing Root Scope**: Use `CherryPickProvider.of(context).rootScope` to interact with the root scope in your widgets.
|
||||||
|
|
||||||
## Features at a Glance
|
```dart
|
||||||
|
final rootScope = CherryPickProvider.of(context).rootScope;
|
||||||
|
```
|
||||||
|
|
||||||
- ⚡ **Fast, lightweight DI** for any Dart/Flutter project
|
### Example Project
|
||||||
- 🧩 **Modular & hierarchical scopes** (root, subscopes)
|
|
||||||
- 🔖 **Named/bound/singleton instances** out of the box
|
|
||||||
- 🔄 **Sync and async provider support**
|
|
||||||
- ✏️ **Runtime parameters for dynamic factory methods**
|
|
||||||
- 🏷️ **Code generator** for annotation-based DI setup (`cherrypick_generator`)
|
|
||||||
- 🕹️ **Deep Flutter integration** via `CherryPickProvider`
|
|
||||||
|
|
||||||
---
|
Check the `example` directory for a complete demonstration of implementing CherryPick Workspace in a Flutter app.
|
||||||
|
|
||||||
## Example Usage
|
## Features
|
||||||
|
|
||||||
Please see:
|
- [x] Dependency Binding and Resolution
|
||||||
- [`cherrypick/README.md`](./cherrypick/README.md) for core DI features and examples
|
- [x] Custom Module Creation
|
||||||
- [`cherrypick_flutter/README.md`](./cherrypick_flutter/README.md) for Flutter-specific usage
|
- [x] Root and Sub-Scopes
|
||||||
- [`cherrypick_annotations/README.md`](./cherrypick_annotations/README.md) and [`cherrypick_generator/README.md`](./cherrypick_generator/README.md) for codegen and annotations
|
- [x] Convenient Root Scope Access in Flutter
|
||||||
|
|
||||||
---
|
## Contributing
|
||||||
|
|
||||||
## Contribution & License
|
We welcome contributions from the community. Feel free to open issues or submit pull requests with suggestions and enhancements.
|
||||||
|
|
||||||
- **Contributions:** PRs, issues, and feedback are welcome on [GitHub](https://github.com/pese-git/cherrypick).
|
## License
|
||||||
- **License:** Apache 2.0 for all packages in this workspace.
|
|
||||||
|
|
||||||
---
|
This project is licensed under the Apache License 2.0. You may obtain a copy of the License at [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
|
||||||
**Happy Cherry Picking! 🍒**
|
## Links
|
||||||
|
|
||||||
|
- [GitHub Repository](https://github.com/pese-git/cherrypick)
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
# benchmark_di
|
|
||||||
|
|
||||||
_Benchmark suite for cherrypick DI container, get_it, and other DI solutions._
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
benchmark_di is a flexible benchmarking suite to compare DI containers (like cherrypick and get_it) on synthetic, deep, and real-world dependency scenarios – chains, factories, async, named, override, etc.
|
|
||||||
|
|
||||||
**Features:**
|
|
||||||
- Universal registration layer and modular scenario setup (works with any DI)
|
|
||||||
- Built-in support for [cherrypick](https://github.com/) and [get_it](https://pub.dev/packages/get_it)
|
|
||||||
- Clean CLI for matrix runs and output formats (Markdown, CSV, JSON, pretty)
|
|
||||||
- Reports metrics: timings, memory (RSS, peak), statistical spreads, and more
|
|
||||||
- Extendable via your own DIAdapter or benchmark scenarios
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Benchmark Scenarios
|
|
||||||
|
|
||||||
- **registerSingleton**: Simple singleton registration/resolution
|
|
||||||
- **chainSingleton**: Resolution of long singleton chains (A→B→C...)
|
|
||||||
- **chainFactory**: Chain resolution via factories (new instances each time)
|
|
||||||
- **asyncChain**: Async chain (with async providers)
|
|
||||||
- **named**: Named/qualified resolution (e.g. from multiple implementations)
|
|
||||||
- **override**: Resolution and override in subScopes/child adapters
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Supported DI
|
|
||||||
|
|
||||||
- **cherrypick** (default)
|
|
||||||
- **get_it**
|
|
||||||
- Easy to add your own DI by creating a DIAdapter
|
|
||||||
|
|
||||||
Switch DI with the CLI option: `--di`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How to Run
|
|
||||||
|
|
||||||
1. **Install dependencies:**
|
|
||||||
```shell
|
|
||||||
dart pub get
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Run all benchmarks (default: all scenarios, 2 warmup, 2 repeats):**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --benchmark=all --format=markdown
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **For get_it:**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --di=getit --benchmark=all --format=markdown
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Show all CLI options:**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --help
|
|
||||||
```
|
|
||||||
|
|
||||||
### CLI Parameters
|
|
||||||
|
|
||||||
- `--di` — DI implementation: `cherrypick` (default) or `getit`
|
|
||||||
- `--benchmark, -b` — Scenario: `registerSingleton`, `chainSingleton`, `chainFactory`, `asyncChain`, `named`, `override`, `all`
|
|
||||||
- `--chainCount, -c` — Number of parallel chains (e.g. `10,100`)
|
|
||||||
- `--nestingDepth, -d` — Chain depth (e.g. `5,10`)
|
|
||||||
- `--repeat, -r` — Measurement repeats (default: 2)
|
|
||||||
- `--warmup, -w` — Warmup runs (default: 1)
|
|
||||||
- `--format, -f` — Output: `pretty`, `csv`, `json`, `markdown`
|
|
||||||
- `--help, -h` — Usage
|
|
||||||
|
|
||||||
### Run Examples
|
|
||||||
|
|
||||||
- **All benchmarks for cherrypick:**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --di=cherrypick --benchmark=all --format=markdown
|
|
||||||
```
|
|
||||||
|
|
||||||
- **For get_it (all scenarios):**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --di=getit --benchmark=all --format=markdown
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Specify chains/depth matrix:**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --benchmark=chainSingleton --chainCount=10,100 --nestingDepth=5,10 --repeat=3 --format=csv
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Universal DI registration: Adapter-centric approach
|
|
||||||
|
|
||||||
Starting from vX.Y.Z, all DI registration scenarios and logic are encapsulated in the adapter itself via the `universalRegistration` method.
|
|
||||||
|
|
||||||
### How to use (in Dart code):
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final di = CherrypickDIAdapter(); // or GetItAdapter(), RiverpodAdapter(), etc
|
|
||||||
|
|
||||||
di.setupDependencies(
|
|
||||||
di.universalRegistration(
|
|
||||||
scenario: UniversalScenario.chain,
|
|
||||||
chainCount: 10,
|
|
||||||
nestingDepth: 5,
|
|
||||||
bindingMode: UniversalBindingMode.singletonStrategy,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
- There is **no more need to use any global function or switch**: each adapter provides its own type-safe implementation.
|
|
||||||
|
|
||||||
### How to add a new scenario or DI:
|
|
||||||
- Implement `universalRegistration<S extends Enum>(...)` in your adapter
|
|
||||||
- Use your own Enum if you want adapter-specific scenarios!
|
|
||||||
- Benchmarks and CLI become automatically extensible for custom DI and scenarios.
|
|
||||||
|
|
||||||
### CLI usage (runs all universal scenarios for Cherrypick, GetIt, Riverpod):
|
|
||||||
|
|
||||||
```
|
|
||||||
dart run bin/main.dart --di=cherrypick --benchmark=all
|
|
||||||
dart run bin/main.dart --di=getit --benchmark=all
|
|
||||||
dart run bin/main.dart --di=riverpod --benchmark=all
|
|
||||||
```
|
|
||||||
|
|
||||||
See the `benchmark_di/lib/di_adapters/` folder for ready-to-use adapters.
|
|
||||||
|
|
||||||
---
|
|
||||||
## Advantages
|
|
||||||
|
|
||||||
- **Type-safe:** Zero dynamic/object usage in DI flows.
|
|
||||||
- **Extensible:** New scenarios are just new Enum values and a method extension.
|
|
||||||
- **No global registration logic:** All DI-related logic is where it belongs: in the adapter.
|
|
||||||
|
|
||||||
=======
|
|
||||||
## How to Add Your Own DI
|
|
||||||
|
|
||||||
1. Implement a class extending `DIAdapter` (`lib/di_adapters/your_adapter.dart`)
|
|
||||||
2. Implement the `universalRegistration<S extends Enum>(...)` method directly in your adapter for type-safe and scenario-specific registration
|
|
||||||
3. Register your adapter in CLI (see `cli/benchmark_cli.dart`)
|
|
||||||
4. No global function needed — all logic is within the adapter!
|
|
||||||
|
|
||||||
---
|
|
||||||
## Universal DI registration: Adapter-centric approach
|
|
||||||
|
|
||||||
Starting from vX.Y.Z, all DI registration scenarios and logic are encapsulated in the adapter itself via the `universalRegistration` method.
|
|
||||||
|
|
||||||
### How to use (in Dart code):
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final di = CherrypickDIAdapter(); // or GetItAdapter(), RiverpodAdapter(), etc
|
|
||||||
|
|
||||||
di.setupDependencies(
|
|
||||||
di.universalRegistration(
|
|
||||||
scenario: UniversalScenario.chain,
|
|
||||||
chainCount: 10,
|
|
||||||
nestingDepth: 5,
|
|
||||||
bindingMode: UniversalBindingMode.singletonStrategy,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
- There is **no more need to use any global function or switch**: each adapter provides its own type-safe implementation.
|
|
||||||
|
|
||||||
### How to add a new scenario or DI:
|
|
||||||
- Implement `universalRegistration<S extends Enum>(...)` in your adapter
|
|
||||||
- Use your own Enum if you want adapter-specific scenarios!
|
|
||||||
- Benchmarks and CLI become automatically extensible for custom DI and scenarios.
|
|
||||||
|
|
||||||
### CLI usage (runs all universal scenarios for Cherrypick, GetIt, Riverpod):
|
|
||||||
|
|
||||||
```
|
|
||||||
dart run bin/main.dart --di=cherrypick --benchmark=all
|
|
||||||
dart run bin/main.dart --di=getit --benchmark=all
|
|
||||||
dart run bin/main.dart --di=riverpod --benchmark=all
|
|
||||||
```
|
|
||||||
|
|
||||||
See the `benchmark_di/lib/di_adapters/` folder for ready-to-use adapters.
|
|
||||||
|
|
||||||
## Advantages
|
|
||||||
|
|
||||||
- **Type-safe:** Zero dynamic/object usage in DI flows.
|
|
||||||
- **Extensible:** New scenarios are just new Enum values and a method extension.
|
|
||||||
- **No global registration logic:** All DI-related logic is where it belongs: in the adapter.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
classDiagram
|
|
||||||
class BenchmarkCliRunner {
|
|
||||||
+run(args)
|
|
||||||
}
|
|
||||||
class UniversalChainBenchmark~TContainer~ {
|
|
||||||
+setup()
|
|
||||||
+run()
|
|
||||||
+teardown()
|
|
||||||
}
|
|
||||||
class UniversalChainAsyncBenchmark~TContainer~ {
|
|
||||||
+setup()
|
|
||||||
+run()
|
|
||||||
+teardown()
|
|
||||||
}
|
|
||||||
class DIAdapter~TContainer~ {
|
|
||||||
<<interface>>
|
|
||||||
+setupDependencies(cb)
|
|
||||||
+resolve~T~(named)
|
|
||||||
+resolveAsync~T~(named)
|
|
||||||
+teardown()
|
|
||||||
+openSubScope(name)
|
|
||||||
+waitForAsyncReady()
|
|
||||||
+universalRegistration<S extends Enum>(...)
|
|
||||||
}
|
|
||||||
class CherrypickDIAdapter
|
|
||||||
class GetItAdapter
|
|
||||||
class RiverpodAdapter
|
|
||||||
class UniversalChainModule {
|
|
||||||
+builder(scope)
|
|
||||||
+chainCount
|
|
||||||
+nestingDepth
|
|
||||||
+bindingMode
|
|
||||||
+scenario
|
|
||||||
}
|
|
||||||
class UniversalService {
|
|
||||||
<<interface>>
|
|
||||||
+value
|
|
||||||
+dependency
|
|
||||||
}
|
|
||||||
class UniversalServiceImpl {
|
|
||||||
+UniversalServiceImpl(value, dependency)
|
|
||||||
}
|
|
||||||
class Scope
|
|
||||||
class UniversalScenario
|
|
||||||
class UniversalBindingMode
|
|
||||||
|
|
||||||
%% Relationships
|
|
||||||
|
|
||||||
BenchmarkCliRunner --> UniversalChainBenchmark
|
|
||||||
BenchmarkCliRunner --> UniversalChainAsyncBenchmark
|
|
||||||
|
|
||||||
UniversalChainBenchmark *-- DIAdapter
|
|
||||||
UniversalChainAsyncBenchmark *-- DIAdapter
|
|
||||||
|
|
||||||
DIAdapter <|.. CherrypickDIAdapter
|
|
||||||
DIAdapter <|.. GetItAdapter
|
|
||||||
DIAdapter <|.. RiverpodAdapter
|
|
||||||
|
|
||||||
CherrypickDIAdapter ..> Scope
|
|
||||||
GetItAdapter ..> GetIt: "uses GetIt"
|
|
||||||
RiverpodAdapter ..> Map~String, ProviderBase~: "uses Provider registry"
|
|
||||||
|
|
||||||
DIAdapter o--> UniversalChainModule : setupDependencies
|
|
||||||
|
|
||||||
UniversalChainModule ..> UniversalScenario
|
|
||||||
UniversalChainModule ..> UniversalBindingMode
|
|
||||||
|
|
||||||
UniversalChainModule o-- UniversalServiceImpl : creates
|
|
||||||
UniversalService <|.. UniversalServiceImpl
|
|
||||||
UniversalServiceImpl --> UniversalService : dependency
|
|
||||||
|
|
||||||
%%
|
|
||||||
%% Each concrete adapter implements universalRegistration<S extends Enum>
|
|
||||||
%% You can add new scenario enums for custom adapters
|
|
||||||
%% Extensibility is achieved via adapter logic, not global functions
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Metrics
|
|
||||||
|
|
||||||
Always collected:
|
|
||||||
- **Timings** (microseconds): mean, median, stddev, min, max
|
|
||||||
- **Memory**: RSS difference, peak RSS
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
# benchmark_di
|
|
||||||
|
|
||||||
_Бенчмаркинговый набор для cherrypick, get_it и других DI-контейнеров._
|
|
||||||
|
|
||||||
## Общее описание
|
|
||||||
|
|
||||||
benchmark_di — это современный фреймворк для измерения производительности DI-контейнеров (как cherrypick, так и get_it) на синтетических, сложных и реальных сценариях: цепочки зависимостей, factory, async, именованные биндинги, override и пр.
|
|
||||||
|
|
||||||
**Возможности:**
|
|
||||||
- Универсальный слой регистрации сценариев (работает с любым DI)
|
|
||||||
- Готовая поддержка [cherrypick](https://github.com/) и [get_it](https://pub.dev/packages/get_it)
|
|
||||||
- Удобный CLI для запусков по матрице значений параметров и различных форматов вывода (Markdown, CSV, JSON, pretty)
|
|
||||||
- Сбор и вывод метрик: время, память (RSS, peak), статистика (среднее, медиана, stddev, min/max)
|
|
||||||
- Легко расширять — создавайте свой DIAdapter и новые сценарии
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Сценарии бенчмарков
|
|
||||||
|
|
||||||
- **registerSingleton**: Регистрация и резолвинг singleton
|
|
||||||
- **chainSingleton**: Разрешение длинных singleton-цепочек (A→B→C…)
|
|
||||||
- **chainFactory**: То же, но с factory (каждый раз — новый объект)
|
|
||||||
- **asyncChain**: Асинхронная цепочка (async factory/provider)
|
|
||||||
- **named**: Разрешение по имени (например, из нескольких реализаций)
|
|
||||||
- **override**: Переопределение зависимостей в subScope
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Поддерживаемые DI-контейнеры
|
|
||||||
|
|
||||||
- **cherrypick** (по умолчанию)
|
|
||||||
- **get_it**
|
|
||||||
- Легко добавить свой DI через DIAdapter
|
|
||||||
|
|
||||||
Меняется одной CLI-опцией: `--di`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Как запустить
|
|
||||||
|
|
||||||
1. **Установить зависимости:**
|
|
||||||
```shell
|
|
||||||
dart pub get
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Запустить все бенчмарки (по умолчанию: все сценарии, 2 прогрева, 2 замера):**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --benchmark=all --format=markdown
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Для get_it:**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --di=getit --benchmark=all --format=markdown
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Показать все опции CLI:**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --help
|
|
||||||
```
|
|
||||||
|
|
||||||
### Параметры CLI
|
|
||||||
|
|
||||||
- `--di` — Какой DI использовать: `cherrypick` (по умолчанию) или `getit`
|
|
||||||
- `--benchmark, -b` — Сценарий: `registerSingleton`, `chainSingleton`, `chainFactory`, `asyncChain`, `named`, `override`, `all`
|
|
||||||
- `--chainCount, -c` — Сколько параллельных цепочек (например, `10,100`)
|
|
||||||
- `--nestingDepth, -d` — Глубина цепочки (например, `5,10`)
|
|
||||||
- `--repeat, -r` — Повторов замера (по умолчанию 2)
|
|
||||||
- `--warmup, -w` — Прогревочных запусков (по умолчанию 1)
|
|
||||||
- `--format, -f` — Формат отчёта: `pretty`, `csv`, `json`, `markdown`
|
|
||||||
- `--help, -h` — Справка
|
|
||||||
|
|
||||||
### Примеры запуска
|
|
||||||
|
|
||||||
- **Все бенчмарки для cherrypick:**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --di=cherrypick --benchmark=all --format=markdown
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Для get_it (все сценарии):**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --di=getit --benchmark=all --format=markdown
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Запуск по матрице параметров:**
|
|
||||||
```shell
|
|
||||||
dart run bin/main.dart --benchmark=chainSingleton --chainCount=10,100 --nestingDepth=5,10 --repeat=3 --format=csv
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Универсальная регистрация зависимостей: теперь через adapter
|
|
||||||
|
|
||||||
В версии X.Y.Z вся логика сценариев регистрации DI-инфраструктуры локализована в adapter через метод `universalRegistration`.
|
|
||||||
|
|
||||||
### Использование в Dart:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
final di = CherrypickDIAdapter(); // или GetItAdapter(), RiverpodAdapter() и т.д.
|
|
||||||
|
|
||||||
di.setupDependencies(
|
|
||||||
di.universalRegistration(
|
|
||||||
scenario: UniversalScenario.chain,
|
|
||||||
chainCount: 10,
|
|
||||||
nestingDepth: 5,
|
|
||||||
bindingMode: UniversalBindingMode.singletonStrategy,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
- **Теперь нет необходимости вызывать глобальные функции или switch-case по типу DI!** Каждый adapter сам предоставляет типобезопасную функцию регистрации.
|
|
||||||
|
|
||||||
### Как добавить новый сценарий или DI:
|
|
||||||
|
|
||||||
- Реализуйте метод `universalRegistration<S extends Enum>(...)` в своём adapter.
|
|
||||||
- Можно использовать как UniversalScenario, так и собственные enum-сценарии!
|
|
||||||
- Бенчмарки CLI автоматически расширяются под ваш DI и ваши сценарии, если реализован метод-расширение.
|
|
||||||
|
|
||||||
### Запуск CLI (все сценарии DI Cherrypick, GetIt, Riverpod):
|
|
||||||
|
|
||||||
```
|
|
||||||
dart run bin/main.dart --di=cherrypick --benchmark=all
|
|
||||||
dart run bin/main.dart --di=getit --benchmark=all
|
|
||||||
dart run bin/main.dart --di=riverpod --benchmark=all
|
|
||||||
```
|
|
||||||
|
|
||||||
Смотрите примеры готовых adapters в `benchmark_di/lib/di_adapters/`.
|
|
||||||
|
|
||||||
## Преимущества
|
|
||||||
|
|
||||||
- **Type-safe:** Исключено использование dynamic/object в стороне DI.
|
|
||||||
- **Масштабируемость:** Новый сценарий — просто enum + метод в adapter.
|
|
||||||
- **Вся логика регистрации теперь только в adapter:** Добавление или изменение не затрагивает глобальные функции.
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Архитектура
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
classDiagram
|
|
||||||
class BenchmarkCliRunner {
|
|
||||||
+run(args)
|
|
||||||
}
|
|
||||||
class UniversalChainBenchmark~TContainer~ {
|
|
||||||
+setup()
|
|
||||||
+run()
|
|
||||||
+teardown()
|
|
||||||
}
|
|
||||||
class UniversalChainAsyncBenchmark~TContainer~ {
|
|
||||||
+setup()
|
|
||||||
+run()
|
|
||||||
+teardown()
|
|
||||||
}
|
|
||||||
class DIAdapter~TContainer~ {
|
|
||||||
<<interface>>
|
|
||||||
+setupDependencies(cb)
|
|
||||||
+resolve~T~(named)
|
|
||||||
+resolveAsync~T~(named)
|
|
||||||
+teardown()
|
|
||||||
+openSubScope(name)
|
|
||||||
+waitForAsyncReady()
|
|
||||||
+universalRegistration<S extends Enum>(...)
|
|
||||||
}
|
|
||||||
class CherrypickDIAdapter
|
|
||||||
class GetItAdapter
|
|
||||||
class RiverpodAdapter
|
|
||||||
class UniversalChainModule {
|
|
||||||
+builder(scope)
|
|
||||||
+chainCount
|
|
||||||
+nestingDepth
|
|
||||||
+bindingMode
|
|
||||||
+scenario
|
|
||||||
}
|
|
||||||
class UniversalService {
|
|
||||||
<<interface>>
|
|
||||||
+value
|
|
||||||
+dependency
|
|
||||||
}
|
|
||||||
class UniversalServiceImpl {
|
|
||||||
+UniversalServiceImpl(value, dependency)
|
|
||||||
}
|
|
||||||
class Scope
|
|
||||||
class UniversalScenario
|
|
||||||
class UniversalBindingMode
|
|
||||||
|
|
||||||
%% Relationships
|
|
||||||
|
|
||||||
BenchmarkCliRunner --> UniversalChainBenchmark
|
|
||||||
BenchmarkCliRunner --> UniversalChainAsyncBenchmark
|
|
||||||
|
|
||||||
UniversalChainBenchmark *-- DIAdapter
|
|
||||||
UniversalChainAsyncBenchmark *-- DIAdapter
|
|
||||||
|
|
||||||
DIAdapter <|.. CherrypickDIAdapter
|
|
||||||
DIAdapter <|.. GetItAdapter
|
|
||||||
DIAdapter <|.. RiverpodAdapter
|
|
||||||
|
|
||||||
CherrypickDIAdapter ..> Scope
|
|
||||||
GetItAdapter ..> GetIt: "uses GetIt"
|
|
||||||
RiverpodAdapter ..> Map~String, ProviderBase~: "uses Provider registry"
|
|
||||||
|
|
||||||
DIAdapter o--> UniversalChainModule : setupDependencies
|
|
||||||
|
|
||||||
UniversalChainModule ..> UniversalScenario
|
|
||||||
UniversalChainModule ..> UniversalBindingMode
|
|
||||||
|
|
||||||
UniversalChainModule o-- UniversalServiceImpl : creates
|
|
||||||
UniversalService <|.. UniversalServiceImpl
|
|
||||||
UniversalServiceImpl --> UniversalService : dependency
|
|
||||||
|
|
||||||
%%
|
|
||||||
%% Each concrete adapter implements universalRegistration<S extends Enum>
|
|
||||||
%% You can add new scenario enums for custom adapters
|
|
||||||
%% Extensibility is achieved via adapter logic, not global functions
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Метрики
|
|
||||||
|
|
||||||
Всегда собираются:
|
|
||||||
- **Время** (мкс): среднее, медиана, stddev, min, max
|
|
||||||
- **Память**: прирост RSS, пиковое значение RSS
|
|
||||||
|
|
||||||
## Лицензия
|
|
||||||
|
|
||||||
MIT
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# Comparative DI Benchmark Report: cherrypick vs get_it vs riverpod
|
|
||||||
|
|
||||||
## Benchmark Scenarios
|
|
||||||
|
|
||||||
1. **RegisterSingleton** — Registers and resolves a singleton. Baseline DI speed.
|
|
||||||
2. **ChainSingleton** — A dependency chain A → B → ... → N (singleton). Deep singleton chain resolution.
|
|
||||||
3. **ChainFactory** — All chain elements are factories. Stateless creation chain.
|
|
||||||
4. **AsyncChain** — Async chain (async factory). Performance on async graphs.
|
|
||||||
5. **Named** — Registers two bindings with names, resolves by name. Named lookup test.
|
|
||||||
6. **Override** — Registers a chain/alias in a child scope. Tests scope overrides.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Comparative Table: chainCount=10, nestingDepth=10 (Mean, PeakRSS)
|
|
||||||
|
|
||||||
| Scenario | cherrypick Mean (us) | cherrypick PeakRSS | get_it Mean (us) | get_it PeakRSS | riverpod Mean (us) | riverpod PeakRSS |
|
|
||||||
|--------------------|---------------------:|-------------------:|-----------------:|---------------:|-------------------:|-----------------:|
|
|
||||||
| RegisterSingleton | 13.00 | 273104 | 8.40 | 261872 | 9.80 | 268512 |
|
|
||||||
| ChainSingleton | 13.80 | 271072 | 2.00 | 262000 | 33.60 | 268784 |
|
|
||||||
| ChainFactory | 5.00 | 299216 | 4.00 | 297136 | 22.80 | 271296 |
|
|
||||||
| AsyncChain | 28.60 | 290640 | 24.60 | 342976 | 78.20 | 285920 |
|
|
||||||
| Named | 2.20 | 297008 | 0.20 | 449824 | 6.20 | 281136 |
|
|
||||||
| Override | 7.00 | 297024 | 0.00 | 449824 | 30.20 | 281152 |
|
|
||||||
|
|
||||||
## Maximum Load: chainCount=100, nestingDepth=100 (Mean, PeakRSS)
|
|
||||||
|
|
||||||
| Scenario | cherrypick Mean (us) | cherrypick PeakRSS | get_it Mean (us) | get_it PeakRSS | riverpod Mean (us) | riverpod PeakRSS |
|
|
||||||
|--------------------|---------------------:|-------------------:|-----------------:|---------------:|-------------------:|-----------------:|
|
|
||||||
| RegisterSingleton | 4.00 | 271072 | 1.00 | 262000 | 2.00 | 268688 |
|
|
||||||
| ChainSingleton | 76.60 | 303312 | 2.00 | 297136 | 221.80 | 270784 |
|
|
||||||
| ChainFactory | 80.00 | 293952 | 39.20 | 342720 | 195.80 | 308640 |
|
|
||||||
| AsyncChain | 251.40 | 297008 | 18.20 | 450640 | 748.80 | 285968 |
|
|
||||||
| Named | 2.20 | 297008 | 0.00 | 449824 | 1.00 | 281136 |
|
|
||||||
| Override | 104.80 | 301632 | 2.20 | 477344 | 120.80 | 294752 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Analysis
|
|
||||||
|
|
||||||
- **get_it** is the absolute leader in all scenarios, especially under deep/nested chains and async.
|
|
||||||
- **cherrypick** is highly competitive and much faster than riverpod on any complex graph.
|
|
||||||
- **riverpod** is only suitable for small/simple DI graphs due to major slowdowns with depth, async, or override.
|
|
||||||
|
|
||||||
### Recommendations
|
|
||||||
- Use **get_it** for performance-critical and deeply nested graphs.
|
|
||||||
- Use **cherrypick** for scalable/testable apps if a small speed loss is acceptable.
|
|
||||||
- Use **riverpod** only if you rely on Flutter integration and your DI chains are simple.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
_Last updated: August 8, 2025._
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# Сравнительный отчет DI-бенчмарка: cherrypick vs get_it vs riverpod
|
|
||||||
|
|
||||||
## Описание сценариев
|
|
||||||
|
|
||||||
1. **RegisterSingleton** — регистрация и получение объекта-синглтона (базовая скорость DI).
|
|
||||||
2. **ChainSingleton** — цепочка зависимостей A → B → ... → N (singleton). Глубокий singleton-резолвинг.
|
|
||||||
3. **ChainFactory** — все элементы цепочки — фабрики. Stateless построение графа.
|
|
||||||
4. **AsyncChain** — асинхронная цепочка (async factory). Тестирует async/await граф.
|
|
||||||
5. **Named** — регистрация двух биндингов с именами, разрешение по имени.
|
|
||||||
6. **Override** — регистрация биндинга/цепочки в дочернем scope. Проверка override/scoping.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Сводная таблица: chainCount=10, nestingDepth=10 (Mean, PeakRSS)
|
|
||||||
|
|
||||||
| Сценарий | cherrypick Mean (мкс) | cherrypick PeakRSS | get_it Mean (мкс) | get_it PeakRSS | riverpod Mean (мкс) | riverpod PeakRSS |
|
|
||||||
|--------------------|----------------------:|-------------------:|------------------:|---------------:|--------------------:|-----------------:|
|
|
||||||
| RegisterSingleton | 13.00 | 273104 | 8.40 | 261872 | 9.80 | 268512 |
|
|
||||||
| ChainSingleton | 13.80 | 271072 | 2.00 | 262000 | 33.60 | 268784 |
|
|
||||||
| ChainFactory | 5.00 | 299216 | 4.00 | 297136 | 22.80 | 271296 |
|
|
||||||
| AsyncChain | 28.60 | 290640 | 24.60 | 342976 | 78.20 | 285920 |
|
|
||||||
| Named | 2.20 | 297008 | 0.20 | 449824 | 6.20 | 281136 |
|
|
||||||
| Override | 7.00 | 297024 | 0.00 | 449824 | 30.20 | 281152 |
|
|
||||||
|
|
||||||
## Максимальная нагрузка: chainCount=100, nestingDepth=100 (Mean, PeakRSS)
|
|
||||||
|
|
||||||
| Сценарий | cherrypick Mean (мкс) | cherrypick PeakRSS | get_it Mean (мкс) | get_it PeakRSS | riverpod Mean (мкс) | riverpod PeakRSS |
|
|
||||||
|--------------------|----------------------:|-------------------:|------------------:|---------------:|--------------------:|-----------------:|
|
|
||||||
| RegisterSingleton | 4.00 | 271072 | 1.00 | 262000 | 2.00 | 268688 |
|
|
||||||
| ChainSingleton | 76.60 | 303312 | 2.00 | 297136 | 221.80 | 270784 |
|
|
||||||
| ChainFactory | 80.00 | 293952 | 39.20 | 342720 | 195.80 | 308640 |
|
|
||||||
| AsyncChain | 251.40 | 297008 | 18.20 | 450640 | 748.80 | 285968 |
|
|
||||||
| Named | 2.20 | 297008 | 0.00 | 449824 | 1.00 | 281136 |
|
|
||||||
| Override | 104.80 | 301632 | 2.20 | 477344 | 120.80 | 294752 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Краткий анализ и рекомендации
|
|
||||||
|
|
||||||
- **get_it** всегда лидер, особенно на глубине/асинхронных графах.
|
|
||||||
- **cherrypick** заметно быстрее riverpod на сложных сценариях, опережая его в разы.
|
|
||||||
- **riverpod** подходит только для простых/небольших графов — при росте глубины или async/override резко проигрывает по скорости.
|
|
||||||
|
|
||||||
### Рекомендации
|
|
||||||
- Используйте **get_it** для критичных к скорости приложений/сложных графов зависимостей.
|
|
||||||
- Выбирайте **cherrypick** для масштабируемых, тестируемых архитектур, если микросекундная разница не критична.
|
|
||||||
- **riverpod** уместен только для реактивного UI или простых графов DI.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
_Обновлено: 8 августа 2025_
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# This file configures the static analysis results for your project (errors,
|
|
||||||
# warnings, and lints).
|
|
||||||
#
|
|
||||||
# This enables the 'recommended' set of lints from `package:lints`.
|
|
||||||
# This set helps identify many issues that may lead to problems when running
|
|
||||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
|
||||||
# style and format.
|
|
||||||
#
|
|
||||||
# If you want a smaller set of lints you can change this to specify
|
|
||||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
|
||||||
# (the recommended set includes the core lints).
|
|
||||||
# The core lints are also what is used by pub.dev for scoring packages.
|
|
||||||
|
|
||||||
include: package:lints/recommended.yaml
|
|
||||||
analyzer:
|
|
||||||
errors:
|
|
||||||
deprecated_member_use: ignore
|
|
||||||
depend_on_referenced_packages: ignore
|
|
||||||
|
|
||||||
# Uncomment the following section to specify additional rules.
|
|
||||||
|
|
||||||
# linter:
|
|
||||||
# rules:
|
|
||||||
# - camel_case_types
|
|
||||||
|
|
||||||
# analyzer:
|
|
||||||
# exclude:
|
|
||||||
# - path/to/excluded/files/**
|
|
||||||
|
|
||||||
# For more information about the core and recommended set of lints, see
|
|
||||||
# https://dart.dev/go/core-lints
|
|
||||||
|
|
||||||
# For additional information about configuring this file, see
|
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import 'package:benchmark_di/cli/benchmark_cli.dart';
|
|
||||||
|
|
||||||
Future<void> main(List<String> args) async {
|
|
||||||
await BenchmarkCliRunner().run(args);
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import 'package:benchmark_di/scenarios/universal_binding_mode.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_scenario.dart';
|
|
||||||
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
||||||
import 'package:benchmark_di/di_adapters/di_adapter.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_service.dart';
|
|
||||||
|
|
||||||
class UniversalChainAsyncBenchmark<TContainer> extends AsyncBenchmarkBase {
|
|
||||||
final DIAdapter<TContainer> di;
|
|
||||||
final int chainCount;
|
|
||||||
final int nestingDepth;
|
|
||||||
final UniversalBindingMode mode;
|
|
||||||
|
|
||||||
UniversalChainAsyncBenchmark(
|
|
||||||
this.di, {
|
|
||||||
this.chainCount = 1,
|
|
||||||
this.nestingDepth = 3,
|
|
||||||
this.mode = UniversalBindingMode.asyncStrategy,
|
|
||||||
}) : super('UniversalAsync: asyncChain/$mode CD=$chainCount/$nestingDepth');
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> setup() async {
|
|
||||||
di.setupDependencies(di.universalRegistration(
|
|
||||||
chainCount: chainCount,
|
|
||||||
nestingDepth: nestingDepth,
|
|
||||||
bindingMode: mode,
|
|
||||||
scenario: UniversalScenario.asyncChain,
|
|
||||||
));
|
|
||||||
await di.waitForAsyncReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> teardown() async {
|
|
||||||
di.teardown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> run() async {
|
|
||||||
final serviceName = '${chainCount}_$nestingDepth';
|
|
||||||
await di.resolveAsync<UniversalService>(named: serviceName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import 'package:benchmark_di/scenarios/universal_binding_mode.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_scenario.dart';
|
|
||||||
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
||||||
import 'package:benchmark_di/di_adapters/di_adapter.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_service.dart';
|
|
||||||
|
|
||||||
class UniversalChainBenchmark<TContainer> extends BenchmarkBase {
|
|
||||||
final DIAdapter<TContainer> _di;
|
|
||||||
final int chainCount;
|
|
||||||
final int nestingDepth;
|
|
||||||
final UniversalBindingMode mode;
|
|
||||||
final UniversalScenario scenario;
|
|
||||||
DIAdapter<TContainer>? _childDi;
|
|
||||||
|
|
||||||
UniversalChainBenchmark(
|
|
||||||
this._di, {
|
|
||||||
this.chainCount = 1,
|
|
||||||
this.nestingDepth = 3,
|
|
||||||
this.mode = UniversalBindingMode.singletonStrategy,
|
|
||||||
this.scenario = UniversalScenario.chain,
|
|
||||||
}) : super('Universal: $scenario/$mode CD=$chainCount/$nestingDepth');
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setup() {
|
|
||||||
switch (scenario) {
|
|
||||||
case UniversalScenario.override:
|
|
||||||
_di.setupDependencies(_di.universalRegistration(
|
|
||||||
chainCount: chainCount,
|
|
||||||
nestingDepth: nestingDepth,
|
|
||||||
bindingMode: UniversalBindingMode.singletonStrategy,
|
|
||||||
scenario: UniversalScenario.chain,
|
|
||||||
));
|
|
||||||
_childDi = _di.openSubScope('child');
|
|
||||||
_childDi!.setupDependencies(_childDi!.universalRegistration(
|
|
||||||
chainCount: chainCount,
|
|
||||||
nestingDepth: nestingDepth,
|
|
||||||
bindingMode: UniversalBindingMode.singletonStrategy,
|
|
||||||
scenario: UniversalScenario.chain,
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
_di.setupDependencies(_di.universalRegistration(
|
|
||||||
chainCount: chainCount,
|
|
||||||
nestingDepth: nestingDepth,
|
|
||||||
bindingMode: mode,
|
|
||||||
scenario: scenario,
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void teardown() => _di.teardown();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void run() {
|
|
||||||
switch (scenario) {
|
|
||||||
case UniversalScenario.register:
|
|
||||||
_di.resolve<UniversalService>();
|
|
||||||
break;
|
|
||||||
case UniversalScenario.named:
|
|
||||||
if (_di.runtimeType.toString().contains('GetItAdapter')) {
|
|
||||||
_di.resolve<UniversalService>(named: 'impl2');
|
|
||||||
} else {
|
|
||||||
_di.resolve<UniversalService>(named: 'impl2');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UniversalScenario.chain:
|
|
||||||
final serviceName = '${chainCount}_$nestingDepth';
|
|
||||||
_di.resolve<UniversalService>(named: serviceName);
|
|
||||||
break;
|
|
||||||
case UniversalScenario.override:
|
|
||||||
_childDi!.resolve<UniversalService>();
|
|
||||||
break;
|
|
||||||
case UniversalScenario.asyncChain:
|
|
||||||
throw UnsupportedError('asyncChain supported only in UniversalChainAsyncBenchmark');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:benchmark_di/cli/report/markdown_report.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_scenario.dart';
|
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
|
||||||
import 'package:get_it/get_it.dart';
|
|
||||||
import 'package:riverpod/riverpod.dart' as rp;
|
|
||||||
|
|
||||||
import 'report/pretty_report.dart';
|
|
||||||
import 'report/csv_report.dart';
|
|
||||||
import 'report/json_report.dart';
|
|
||||||
import 'parser.dart';
|
|
||||||
import 'runner.dart';
|
|
||||||
import 'package:benchmark_di/benchmarks/universal_chain_benchmark.dart';
|
|
||||||
import 'package:benchmark_di/benchmarks/universal_chain_async_benchmark.dart';
|
|
||||||
import 'package:benchmark_di/di_adapters/cherrypick_adapter.dart';
|
|
||||||
import 'package:benchmark_di/di_adapters/get_it_adapter.dart';
|
|
||||||
import 'package:benchmark_di/di_adapters/riverpod_adapter.dart';
|
|
||||||
|
|
||||||
/// Command-line interface (CLI) runner for benchmarks.
|
|
||||||
///
|
|
||||||
/// Parses CLI arguments, orchestrates benchmarks for different
|
|
||||||
/// scenarios and configurations, collects results, and generates reports
|
|
||||||
/// in the desired output format.
|
|
||||||
class BenchmarkCliRunner {
|
|
||||||
/// Runs benchmarks based on CLI [args], configuring different test scenarios.
|
|
||||||
Future<void> run(List<String> args) async {
|
|
||||||
final config = parseBenchmarkCli(args);
|
|
||||||
final results = <Map<String, dynamic>>[];
|
|
||||||
for (final bench in config.benchesToRun) {
|
|
||||||
final scenario = toScenario(bench);
|
|
||||||
final mode = toMode(bench);
|
|
||||||
for (final c in config.chainCounts) {
|
|
||||||
for (final d in config.nestDepths) {
|
|
||||||
BenchmarkResult benchResult;
|
|
||||||
if (config.di == 'getit') {
|
|
||||||
final di = GetItAdapter();
|
|
||||||
if (scenario == UniversalScenario.asyncChain) {
|
|
||||||
final benchAsync = UniversalChainAsyncBenchmark<GetIt>(di,
|
|
||||||
chainCount: c, nestingDepth: d, mode: mode,
|
|
||||||
);
|
|
||||||
benchResult = await BenchmarkRunner.runAsync(
|
|
||||||
benchmark: benchAsync,
|
|
||||||
warmups: config.warmups,
|
|
||||||
repeats: config.repeats,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
final benchSync = UniversalChainBenchmark<GetIt>(di,
|
|
||||||
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
|
|
||||||
);
|
|
||||||
benchResult = await BenchmarkRunner.runSync(
|
|
||||||
benchmark: benchSync,
|
|
||||||
warmups: config.warmups,
|
|
||||||
repeats: config.repeats,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (config.di == 'riverpod') {
|
|
||||||
final di = RiverpodAdapter();
|
|
||||||
if (scenario == UniversalScenario.asyncChain) {
|
|
||||||
final benchAsync = UniversalChainAsyncBenchmark<Map<String, rp.ProviderBase<Object?>>>(di,
|
|
||||||
chainCount: c, nestingDepth: d, mode: mode,
|
|
||||||
);
|
|
||||||
benchResult = await BenchmarkRunner.runAsync(
|
|
||||||
benchmark: benchAsync,
|
|
||||||
warmups: config.warmups,
|
|
||||||
repeats: config.repeats,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
final benchSync = UniversalChainBenchmark<Map<String, rp.ProviderBase<Object?>>>(di,
|
|
||||||
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
|
|
||||||
);
|
|
||||||
benchResult = await BenchmarkRunner.runSync(
|
|
||||||
benchmark: benchSync,
|
|
||||||
warmups: config.warmups,
|
|
||||||
repeats: config.repeats,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final di = CherrypickDIAdapter();
|
|
||||||
if (scenario == UniversalScenario.asyncChain) {
|
|
||||||
final benchAsync = UniversalChainAsyncBenchmark<Scope>(di,
|
|
||||||
chainCount: c, nestingDepth: d, mode: mode,
|
|
||||||
);
|
|
||||||
benchResult = await BenchmarkRunner.runAsync(
|
|
||||||
benchmark: benchAsync,
|
|
||||||
warmups: config.warmups,
|
|
||||||
repeats: config.repeats,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
final benchSync = UniversalChainBenchmark<Scope>(di,
|
|
||||||
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
|
|
||||||
);
|
|
||||||
benchResult = await BenchmarkRunner.runSync(
|
|
||||||
benchmark: benchSync,
|
|
||||||
warmups: config.warmups,
|
|
||||||
repeats: config.repeats,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final timings = benchResult.timings;
|
|
||||||
timings.sort();
|
|
||||||
var mean = timings.reduce((a, b) => a + b) / timings.length;
|
|
||||||
var median = timings[timings.length ~/ 2];
|
|
||||||
var minVal = timings.first;
|
|
||||||
var maxVal = timings.last;
|
|
||||||
var stddev = timings.isEmpty ? 0 : sqrt(timings.map((x) => pow(x - mean, 2)).reduce((a, b) => a + b) / timings.length);
|
|
||||||
results.add({
|
|
||||||
'benchmark': 'Universal_$bench',
|
|
||||||
'chainCount': c,
|
|
||||||
'nestingDepth': d,
|
|
||||||
'mean_us': mean.toStringAsFixed(2),
|
|
||||||
'median_us': median.toStringAsFixed(2),
|
|
||||||
'stddev_us': stddev.toStringAsFixed(2),
|
|
||||||
'min_us': minVal.toStringAsFixed(2),
|
|
||||||
'max_us': maxVal.toStringAsFixed(2),
|
|
||||||
'trials': timings.length,
|
|
||||||
'timings_us': timings.map((t) => t.toStringAsFixed(2)).toList(),
|
|
||||||
'memory_diff_kb': benchResult.memoryDiffKb,
|
|
||||||
'delta_peak_kb': benchResult.deltaPeakKb,
|
|
||||||
'peak_rss_kb': benchResult.peakRssKb,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final reportGenerators = {
|
|
||||||
'pretty': PrettyReport(),
|
|
||||||
'csv': CsvReport(),
|
|
||||||
'json': JsonReport(),
|
|
||||||
'markdown': MarkdownReport(),
|
|
||||||
};
|
|
||||||
print(reportGenerators[config.format]?.render(results) ?? PrettyReport().render(results));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:args/args.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_binding_mode.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_scenario.dart';
|
|
||||||
|
|
||||||
/// Enum describing all supported Universal DI benchmark types.
|
|
||||||
enum UniversalBenchmark {
|
|
||||||
/// Simple singleton registration benchmark
|
|
||||||
registerSingleton,
|
|
||||||
/// Chain of singleton dependencies
|
|
||||||
chainSingleton,
|
|
||||||
/// Chain using factories
|
|
||||||
chainFactory,
|
|
||||||
/// Async chain resolution
|
|
||||||
chainAsync,
|
|
||||||
/// Named registration benchmark
|
|
||||||
named,
|
|
||||||
/// Override/child-scope benchmark
|
|
||||||
override,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps [UniversalBenchmark] to the scenario enum for DI chains.
|
|
||||||
UniversalScenario toScenario(UniversalBenchmark b) {
|
|
||||||
switch (b) {
|
|
||||||
case UniversalBenchmark.registerSingleton:
|
|
||||||
return UniversalScenario.register;
|
|
||||||
case UniversalBenchmark.chainSingleton:
|
|
||||||
return UniversalScenario.chain;
|
|
||||||
case UniversalBenchmark.chainFactory:
|
|
||||||
return UniversalScenario.chain;
|
|
||||||
case UniversalBenchmark.chainAsync:
|
|
||||||
return UniversalScenario.asyncChain;
|
|
||||||
case UniversalBenchmark.named:
|
|
||||||
return UniversalScenario.named;
|
|
||||||
case UniversalBenchmark.override:
|
|
||||||
return UniversalScenario.override;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps benchmark to registration mode (singleton/factory/async).
|
|
||||||
UniversalBindingMode toMode(UniversalBenchmark b) {
|
|
||||||
switch (b) {
|
|
||||||
case UniversalBenchmark.registerSingleton:
|
|
||||||
return UniversalBindingMode.singletonStrategy;
|
|
||||||
case UniversalBenchmark.chainSingleton:
|
|
||||||
return UniversalBindingMode.singletonStrategy;
|
|
||||||
case UniversalBenchmark.chainFactory:
|
|
||||||
return UniversalBindingMode.factoryStrategy;
|
|
||||||
case UniversalBenchmark.chainAsync:
|
|
||||||
return UniversalBindingMode.asyncStrategy;
|
|
||||||
case UniversalBenchmark.named:
|
|
||||||
return UniversalBindingMode.singletonStrategy;
|
|
||||||
case UniversalBenchmark.override:
|
|
||||||
return UniversalBindingMode.singletonStrategy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility to parse a string into its corresponding enum value [T].
|
|
||||||
T parseEnum<T>(String value, List<T> values, T defaultValue) {
|
|
||||||
return values.firstWhere(
|
|
||||||
(v) => v.toString().split('.').last.toLowerCase() == value.toLowerCase(),
|
|
||||||
orElse: () => defaultValue,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses comma-separated integer list from [s].
|
|
||||||
List<int> parseIntList(String s) =>
|
|
||||||
s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList();
|
|
||||||
|
|
||||||
/// CLI config describing what and how to benchmark.
|
|
||||||
class BenchmarkCliConfig {
|
|
||||||
/// Benchmarks enabled to run (scenarios).
|
|
||||||
final List<UniversalBenchmark> benchesToRun;
|
|
||||||
/// List of chain counts (parallel, per test).
|
|
||||||
final List<int> chainCounts;
|
|
||||||
/// List of nesting depths (max chain length, per test).
|
|
||||||
final List<int> nestDepths;
|
|
||||||
/// How many times to repeat each trial.
|
|
||||||
final int repeats;
|
|
||||||
/// How many times to warm-up before measuring.
|
|
||||||
final int warmups;
|
|
||||||
/// Output report format.
|
|
||||||
final String format;
|
|
||||||
/// Name of DI implementation ("cherrypick" or "getit")
|
|
||||||
final String di;
|
|
||||||
BenchmarkCliConfig({
|
|
||||||
required this.benchesToRun,
|
|
||||||
required this.chainCounts,
|
|
||||||
required this.nestDepths,
|
|
||||||
required this.repeats,
|
|
||||||
required this.warmups,
|
|
||||||
required this.format,
|
|
||||||
required this.di,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses CLI arguments [args] into a [BenchmarkCliConfig].
|
|
||||||
/// Supports --benchmark, --chainCount, --nestingDepth, etc.
|
|
||||||
BenchmarkCliConfig parseBenchmarkCli(List<String> args) {
|
|
||||||
final parser = ArgParser()
|
|
||||||
..addOption('benchmark', abbr: 'b', defaultsTo: 'chainSingleton')
|
|
||||||
..addOption('chainCount', abbr: 'c', defaultsTo: '10')
|
|
||||||
..addOption('nestingDepth', abbr: 'd', defaultsTo: '5')
|
|
||||||
..addOption('repeat', abbr: 'r', defaultsTo: '2')
|
|
||||||
..addOption('warmup', abbr: 'w', defaultsTo: '1')
|
|
||||||
..addOption('format', abbr: 'f', defaultsTo: 'pretty')
|
|
||||||
..addOption('di', defaultsTo: 'cherrypick', help: 'DI implementation: cherrypick, getit or riverpod')
|
|
||||||
..addFlag('help', abbr: 'h', negatable: false, help: 'Show help');
|
|
||||||
final result = parser.parse(args);
|
|
||||||
if (result['help'] == true) {
|
|
||||||
print(parser.usage);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
final benchName = result['benchmark'] as String;
|
|
||||||
final isAll = benchName == 'all';
|
|
||||||
final allBenches = UniversalBenchmark.values;
|
|
||||||
final benchesToRun = isAll
|
|
||||||
? allBenches
|
|
||||||
: [parseEnum(benchName, allBenches, UniversalBenchmark.chainSingleton)];
|
|
||||||
return BenchmarkCliConfig(
|
|
||||||
benchesToRun: benchesToRun,
|
|
||||||
chainCounts: parseIntList(result['chainCount'] as String),
|
|
||||||
nestDepths: parseIntList(result['nestingDepth'] as String),
|
|
||||||
repeats: int.tryParse(result['repeat'] as String? ?? "") ?? 2,
|
|
||||||
warmups: int.tryParse(result['warmup'] as String? ?? "") ?? 1,
|
|
||||||
format: result['format'] as String,
|
|
||||||
di: result['di'] as String? ?? 'cherrypick',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import 'report_generator.dart';
|
|
||||||
|
|
||||||
/// Generates a CSV-formatted report for benchmark results.
|
|
||||||
class CsvReport extends ReportGenerator {
|
|
||||||
/// List of all keys/columns to include in the CSV output.
|
|
||||||
@override
|
|
||||||
final List<String> keys = [
|
|
||||||
'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us',
|
|
||||||
'min_us','max_us','trials','timings_us','memory_diff_kb','delta_peak_kb','peak_rss_kb'
|
|
||||||
];
|
|
||||||
/// Renders rows as a CSV table string.
|
|
||||||
@override
|
|
||||||
String render(List<Map<String, dynamic>> rows) {
|
|
||||||
final header = keys.join(',');
|
|
||||||
final lines = rows.map((r) =>
|
|
||||||
keys.map((k) {
|
|
||||||
final v = r[k];
|
|
||||||
if (v is List) return '"${v.join(';')}"';
|
|
||||||
return (v ?? '').toString();
|
|
||||||
}).join(',')
|
|
||||||
).toList();
|
|
||||||
return ([header] + lines).join('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import 'report_generator.dart';
|
|
||||||
|
|
||||||
/// Generates a JSON-formatted report for benchmark results.
|
|
||||||
class JsonReport extends ReportGenerator {
|
|
||||||
/// No specific keys; outputs all fields in raw map.
|
|
||||||
@override
|
|
||||||
List<String> get keys => [];
|
|
||||||
/// Renders all result rows as a pretty-printed JSON array.
|
|
||||||
@override
|
|
||||||
String render(List<Map<String, dynamic>> rows) {
|
|
||||||
return '[\n${rows.map((r) => ' $r').join(',\n')}\n]';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import 'report_generator.dart';
|
|
||||||
|
|
||||||
/// Generates a Markdown-formatted report for benchmark results.
|
|
||||||
///
|
|
||||||
/// Displays result rows as a visually clear Markdown table including a legend for all metrics.
|
|
||||||
class MarkdownReport extends ReportGenerator {
|
|
||||||
/// List of columns (keys) to show in the Markdown table.
|
|
||||||
@override
|
|
||||||
final List<String> keys = [
|
|
||||||
'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us',
|
|
||||||
'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb'
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Friendly display names for each benchmark type.
|
|
||||||
static const nameMap = {
|
|
||||||
'Universal_UniversalBenchmark.registerSingleton':'RegisterSingleton',
|
|
||||||
'Universal_UniversalBenchmark.chainSingleton':'ChainSingleton',
|
|
||||||
'Universal_UniversalBenchmark.chainFactory':'ChainFactory',
|
|
||||||
'Universal_UniversalBenchmark.chainAsync':'AsyncChain',
|
|
||||||
'Universal_UniversalBenchmark.named':'Named',
|
|
||||||
'Universal_UniversalBenchmark.override':'Override',
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Renders all results as a formatted Markdown table with aligned columns and a legend.
|
|
||||||
@override
|
|
||||||
String render(List<Map<String, dynamic>> rows) {
|
|
||||||
final headers = [
|
|
||||||
'Benchmark', 'Chain Count', 'Depth', 'Mean (us)', 'Median', 'Stddev', 'Min', 'Max', 'N', 'ΔRSS(KB)', 'ΔPeak(KB)', 'PeakRSS(KB)'
|
|
||||||
];
|
|
||||||
final dataRows = rows.map((r) {
|
|
||||||
final readableName = nameMap[r['benchmark']] ?? r['benchmark'];
|
|
||||||
return [
|
|
||||||
readableName,
|
|
||||||
r['chainCount'],
|
|
||||||
r['nestingDepth'],
|
|
||||||
r['mean_us'],
|
|
||||||
r['median_us'],
|
|
||||||
r['stddev_us'],
|
|
||||||
r['min_us'],
|
|
||||||
r['max_us'],
|
|
||||||
r['trials'],
|
|
||||||
r['memory_diff_kb'],
|
|
||||||
r['delta_peak_kb'],
|
|
||||||
r['peak_rss_kb'],
|
|
||||||
].map((cell) => cell.toString()).toList();
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
// Calculate column width for pretty alignment
|
|
||||||
final all = [headers] + dataRows;
|
|
||||||
final widths = List.generate(headers.length, (i) {
|
|
||||||
return all.map((row) => row[i].length).reduce((a, b) => a > b ? a : b);
|
|
||||||
});
|
|
||||||
|
|
||||||
String rowToLine(List<String> row, {String sep = ' | '}) =>
|
|
||||||
'| ${List.generate(row.length, (i) => row[i].padRight(widths[i])).join(sep)} |';
|
|
||||||
|
|
||||||
final headerLine = rowToLine(headers);
|
|
||||||
final divider = '| ${widths.map((w) => '-' * w).join(' | ')} |';
|
|
||||||
final lines = dataRows.map(rowToLine).toList();
|
|
||||||
|
|
||||||
final legend = '''
|
|
||||||
> **Legend:**
|
|
||||||
> `Benchmark` – Test name
|
|
||||||
> `Chain Count` – Number of independent chains
|
|
||||||
> `Depth` – Depth of each chain
|
|
||||||
> `Mean (us)` – Average time per run (microseconds)
|
|
||||||
> `Median` – Median time per run
|
|
||||||
> `Stddev` – Standard deviation
|
|
||||||
> `Min`, `Max` – Min/max run time
|
|
||||||
> `N` – Number of measurements
|
|
||||||
> `ΔRSS(KB)` – Change in process memory (KB)
|
|
||||||
> `ΔPeak(KB)` – Change in peak RSS (KB)
|
|
||||||
> `PeakRSS(KB)` – Max observed RSS memory (KB)
|
|
||||||
''';
|
|
||||||
|
|
||||||
return '$legend\n\n${([headerLine, divider] + lines).join('\n')}' ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import 'report_generator.dart';
|
|
||||||
|
|
||||||
/// Generates a human-readable, tab-delimited report for benchmark results.
|
|
||||||
///
|
|
||||||
/// Used for terminal and log output; shows each result as a single line with labeled headers.
|
|
||||||
class PrettyReport extends ReportGenerator {
|
|
||||||
/// List of columns to output in the pretty report.
|
|
||||||
@override
|
|
||||||
final List<String> keys = [
|
|
||||||
'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us',
|
|
||||||
'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb'
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Mappings from internal benchmark IDs to display names.
|
|
||||||
static const nameMap = {
|
|
||||||
'Universal_UniversalBenchmark.registerSingleton': 'RegisterSingleton',
|
|
||||||
'Universal_UniversalBenchmark.chainSingleton': 'ChainSingleton',
|
|
||||||
'Universal_UniversalBenchmark.chainFactory': 'ChainFactory',
|
|
||||||
'Universal_UniversalBenchmark.chainAsync': 'AsyncChain',
|
|
||||||
'Universal_UniversalBenchmark.named': 'Named',
|
|
||||||
'Universal_UniversalBenchmark.override': 'Override',
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Renders the results as a header + tab-separated value table.
|
|
||||||
@override
|
|
||||||
String render(List<Map<String, dynamic>> rows) {
|
|
||||||
final headers = [
|
|
||||||
'Benchmark', 'Chain Count', 'Depth', 'Mean (us)', 'Median', 'Stddev', 'Min', 'Max', 'N', 'ΔRSS(KB)', 'ΔPeak(KB)', 'PeakRSS(KB)'
|
|
||||||
];
|
|
||||||
final header = headers.join('\t');
|
|
||||||
final lines = rows.map((r) {
|
|
||||||
final readableName = nameMap[r['benchmark']] ?? r['benchmark'];
|
|
||||||
return [
|
|
||||||
readableName,
|
|
||||||
r['chainCount'],
|
|
||||||
r['nestingDepth'],
|
|
||||||
r['mean_us'],
|
|
||||||
r['median_us'],
|
|
||||||
r['stddev_us'],
|
|
||||||
r['min_us'],
|
|
||||||
r['max_us'],
|
|
||||||
r['trials'],
|
|
||||||
r['memory_diff_kb'],
|
|
||||||
r['delta_peak_kb'],
|
|
||||||
r['peak_rss_kb'],
|
|
||||||
].join('\t');
|
|
||||||
}).toList();
|
|
||||||
return ([header] + lines).join('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/// Abstract base for generating benchmark result reports in different formats.
|
|
||||||
///
|
|
||||||
/// Subclasses implement [render] to output results, and [keys] to define columns (if any).
|
|
||||||
abstract class ReportGenerator {
|
|
||||||
/// Renders the given [results] as a formatted string (table, markdown, csv, etc).
|
|
||||||
String render(List<Map<String, dynamic>> results);
|
|
||||||
/// List of output columns/keys included in the export (or [] for auto/all).
|
|
||||||
List<String> get keys;
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
import 'dart:math';
|
|
||||||
import 'package:benchmark_di/benchmarks/universal_chain_benchmark.dart';
|
|
||||||
import 'package:benchmark_di/benchmarks/universal_chain_async_benchmark.dart';
|
|
||||||
|
|
||||||
/// Holds the results for a single benchmark execution.
|
|
||||||
class BenchmarkResult {
|
|
||||||
/// List of timings for each run (in microseconds).
|
|
||||||
final List<num> timings;
|
|
||||||
/// Difference in memory (RSS, in KB) after running.
|
|
||||||
final int memoryDiffKb;
|
|
||||||
/// Difference between peak RSS and initial RSS (in KB).
|
|
||||||
final int deltaPeakKb;
|
|
||||||
/// Peak RSS memory observed (in KB).
|
|
||||||
final int peakRssKb;
|
|
||||||
BenchmarkResult({
|
|
||||||
required this.timings,
|
|
||||||
required this.memoryDiffKb,
|
|
||||||
required this.deltaPeakKb,
|
|
||||||
required this.peakRssKb,
|
|
||||||
});
|
|
||||||
/// Computes a BenchmarkResult instance from run timings and memory data.
|
|
||||||
factory BenchmarkResult.collect({
|
|
||||||
required List<num> timings,
|
|
||||||
required List<int> rssValues,
|
|
||||||
required int memBefore,
|
|
||||||
}) {
|
|
||||||
final memAfter = ProcessInfo.currentRss;
|
|
||||||
final memDiffKB = ((memAfter - memBefore) / 1024).round();
|
|
||||||
final peakRss = [...rssValues, memBefore].reduce(max);
|
|
||||||
final deltaPeakKb = ((peakRss - memBefore) / 1024).round();
|
|
||||||
return BenchmarkResult(
|
|
||||||
timings: timings,
|
|
||||||
memoryDiffKb: memDiffKB,
|
|
||||||
deltaPeakKb: deltaPeakKb,
|
|
||||||
peakRssKb: (peakRss / 1024).round(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Static methods to execute and time benchmarks for DI containers.
|
|
||||||
class BenchmarkRunner {
|
|
||||||
/// Runs a synchronous benchmark ([UniversalChainBenchmark]) for a given number of [warmups] and [repeats].
|
|
||||||
/// Collects execution time and observed memory.
|
|
||||||
static Future<BenchmarkResult> runSync({
|
|
||||||
required UniversalChainBenchmark benchmark,
|
|
||||||
required int warmups,
|
|
||||||
required int repeats,
|
|
||||||
}) async {
|
|
||||||
final timings = <num>[];
|
|
||||||
final rssValues = <int>[];
|
|
||||||
for (int i = 0; i < warmups; i++) {
|
|
||||||
benchmark.setup();
|
|
||||||
benchmark.run();
|
|
||||||
benchmark.teardown();
|
|
||||||
}
|
|
||||||
final memBefore = ProcessInfo.currentRss;
|
|
||||||
for (int i = 0; i < repeats; i++) {
|
|
||||||
benchmark.setup();
|
|
||||||
final sw = Stopwatch()..start();
|
|
||||||
benchmark.run();
|
|
||||||
sw.stop();
|
|
||||||
timings.add(sw.elapsedMicroseconds);
|
|
||||||
rssValues.add(ProcessInfo.currentRss);
|
|
||||||
benchmark.teardown();
|
|
||||||
}
|
|
||||||
return BenchmarkResult.collect(timings: timings, rssValues: rssValues, memBefore: memBefore);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs an asynchronous benchmark ([UniversalChainAsyncBenchmark]) for a given number of [warmups] and [repeats].
|
|
||||||
/// Collects execution time and observed memory.
|
|
||||||
static Future<BenchmarkResult> runAsync({
|
|
||||||
required UniversalChainAsyncBenchmark benchmark,
|
|
||||||
required int warmups,
|
|
||||||
required int repeats,
|
|
||||||
}) async {
|
|
||||||
final timings = <num>[];
|
|
||||||
final rssValues = <int>[];
|
|
||||||
for (int i = 0; i < warmups; i++) {
|
|
||||||
await benchmark.setup();
|
|
||||||
await benchmark.run();
|
|
||||||
await benchmark.teardown();
|
|
||||||
}
|
|
||||||
final memBefore = ProcessInfo.currentRss;
|
|
||||||
for (int i = 0; i < repeats; i++) {
|
|
||||||
await benchmark.setup();
|
|
||||||
final sw = Stopwatch()..start();
|
|
||||||
await benchmark.run();
|
|
||||||
sw.stop();
|
|
||||||
timings.add(sw.elapsedMicroseconds);
|
|
||||||
rssValues.add(ProcessInfo.currentRss);
|
|
||||||
await benchmark.teardown();
|
|
||||||
}
|
|
||||||
return BenchmarkResult.collect(timings: timings, rssValues: rssValues, memBefore: memBefore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
import 'package:benchmark_di/scenarios/universal_binding_mode.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_scenario.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_service.dart';
|
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
|
||||||
import 'di_adapter.dart';
|
|
||||||
|
|
||||||
|
|
||||||
/// Test module that generates a chain of service bindings for benchmarking.
|
|
||||||
///
|
|
||||||
/// Configurable by chain count, nesting depth, binding mode, and scenario
|
|
||||||
/// to support various DI performance tests (singleton, factory, async, etc).
|
|
||||||
class UniversalChainModule extends Module {
|
|
||||||
/// Number of chains to create.
|
|
||||||
final int chainCount;
|
|
||||||
/// Depth of each chain.
|
|
||||||
final int nestingDepth;
|
|
||||||
/// How modules are registered (factory/singleton/async).
|
|
||||||
final UniversalBindingMode bindingMode;
|
|
||||||
/// Which di scenario to generate (chained, named, etc).
|
|
||||||
final UniversalScenario scenario;
|
|
||||||
|
|
||||||
/// Constructs a configured test DI module for the benchmarks.
|
|
||||||
UniversalChainModule({
|
|
||||||
required this.chainCount,
|
|
||||||
required this.nestingDepth,
|
|
||||||
this.bindingMode = UniversalBindingMode.singletonStrategy,
|
|
||||||
this.scenario = UniversalScenario.chain,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
void builder(Scope currentScope) {
|
|
||||||
if (scenario == UniversalScenario.asyncChain) {
|
|
||||||
// Generate async chain with singleton async bindings.
|
|
||||||
for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) {
|
|
||||||
for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) {
|
|
||||||
final chain = chainIndex + 1;
|
|
||||||
final level = levelIndex + 1;
|
|
||||||
final prevDepName = '${chain}_${level - 1}';
|
|
||||||
final depName = '${chain}_$level';
|
|
||||||
bind<UniversalService>()
|
|
||||||
.toProvideAsync(() async {
|
|
||||||
final prev = level > 1
|
|
||||||
? await currentScope.resolveAsync<UniversalService>(named: prevDepName)
|
|
||||||
: null;
|
|
||||||
return UniversalServiceImpl(
|
|
||||||
value: depName,
|
|
||||||
dependency: prev,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.withName(depName)
|
|
||||||
.singleton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (scenario) {
|
|
||||||
case UniversalScenario.register:
|
|
||||||
// Simple singleton registration.
|
|
||||||
bind<UniversalService>()
|
|
||||||
.toProvide(() => UniversalServiceImpl(value: 'reg', dependency: null))
|
|
||||||
.singleton();
|
|
||||||
break;
|
|
||||||
case UniversalScenario.named:
|
|
||||||
// Named factory registration for two distinct objects.
|
|
||||||
bind<UniversalService>().toProvide(() => UniversalServiceImpl(value: 'impl1')).withName('impl1');
|
|
||||||
bind<UniversalService>().toProvide(() => UniversalServiceImpl(value: 'impl2')).withName('impl2');
|
|
||||||
break;
|
|
||||||
case UniversalScenario.chain:
|
|
||||||
// Chain of nested services, with dependency on previous level by name.
|
|
||||||
for (var chainIndex = 0; chainIndex < chainCount; chainIndex++) {
|
|
||||||
for (var levelIndex = 0; levelIndex < nestingDepth; levelIndex++) {
|
|
||||||
final chain = chainIndex + 1;
|
|
||||||
final level = levelIndex + 1;
|
|
||||||
final prevDepName = '${chain}_${level - 1}';
|
|
||||||
final depName = '${chain}_$level';
|
|
||||||
switch (bindingMode) {
|
|
||||||
case UniversalBindingMode.singletonStrategy:
|
|
||||||
bind<UniversalService>()
|
|
||||||
.toProvide(() => UniversalServiceImpl(
|
|
||||||
value: depName,
|
|
||||||
dependency: currentScope.tryResolve<UniversalService>(named: prevDepName),
|
|
||||||
))
|
|
||||||
.withName(depName)
|
|
||||||
.singleton();
|
|
||||||
break;
|
|
||||||
case UniversalBindingMode.factoryStrategy:
|
|
||||||
bind<UniversalService>()
|
|
||||||
.toProvide(() => UniversalServiceImpl(
|
|
||||||
value: depName,
|
|
||||||
dependency: currentScope.tryResolve<UniversalService>(named: prevDepName),
|
|
||||||
))
|
|
||||||
.withName(depName);
|
|
||||||
break;
|
|
||||||
case UniversalBindingMode.asyncStrategy:
|
|
||||||
bind<UniversalService>()
|
|
||||||
.toProvideAsync(() async => UniversalServiceImpl(
|
|
||||||
value: depName,
|
|
||||||
dependency: await currentScope.resolveAsync<UniversalService>(named: prevDepName),
|
|
||||||
))
|
|
||||||
.withName(depName)
|
|
||||||
.singleton();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Регистрация алиаса без имени (на последний элемент цепочки)
|
|
||||||
final depName = '${chainCount}_$nestingDepth';
|
|
||||||
bind<UniversalService>()
|
|
||||||
.toProvide(() => currentScope.resolve<UniversalService>(named: depName))
|
|
||||||
.singleton();
|
|
||||||
break;
|
|
||||||
case UniversalScenario.override:
|
|
||||||
// handled at benchmark level, но алиас нужен прямо в этом scope!
|
|
||||||
final depName = '${chainCount}_$nestingDepth';
|
|
||||||
bind<UniversalService>()
|
|
||||||
.toProvide(() => currentScope.resolve<UniversalService>(named: depName))
|
|
||||||
.singleton();
|
|
||||||
break;
|
|
||||||
case UniversalScenario.asyncChain:
|
|
||||||
// already handled above
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CherrypickDIAdapter extends DIAdapter<Scope> {
|
|
||||||
Scope? _scope;
|
|
||||||
final bool _isSubScope;
|
|
||||||
|
|
||||||
CherrypickDIAdapter([Scope? scope, this._isSubScope = false]) {
|
|
||||||
_scope = scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setupDependencies(void Function(Scope container) registration) {
|
|
||||||
_scope ??= CherryPick.openRootScope();
|
|
||||||
registration(_scope!);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Registration<Scope> universalRegistration<S extends Enum>({
|
|
||||||
required S scenario,
|
|
||||||
required int chainCount,
|
|
||||||
required int nestingDepth,
|
|
||||||
required UniversalBindingMode bindingMode,
|
|
||||||
}) {
|
|
||||||
if (scenario is UniversalScenario) {
|
|
||||||
return (scope) {
|
|
||||||
scope.installModules([
|
|
||||||
UniversalChainModule(
|
|
||||||
chainCount: chainCount,
|
|
||||||
nestingDepth: nestingDepth,
|
|
||||||
bindingMode: bindingMode,
|
|
||||||
scenario: scenario,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw UnsupportedError('Scenario $scenario not supported by CherrypickDIAdapter');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
T resolve<T extends Object>({String? named}) =>
|
|
||||||
_scope!.resolve<T>(named: named);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<T> resolveAsync<T extends Object>({String? named}) async =>
|
|
||||||
_scope!.resolveAsync<T>(named: named);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void teardown() {
|
|
||||||
if (!_isSubScope) {
|
|
||||||
CherryPick.closeRootScope();
|
|
||||||
_scope = null;
|
|
||||||
}
|
|
||||||
// SubScope teardown не требуется
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
CherrypickDIAdapter openSubScope(String name) {
|
|
||||||
return CherrypickDIAdapter(_scope!.openSubScope(name), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> waitForAsyncReady() async {}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import 'package:benchmark_di/scenarios/universal_binding_mode.dart';
|
|
||||||
/// Универсальная абстракция для DI-адаптера с унифицированной функцией регистрации.
|
|
||||||
/// Теперь для каждого адаптера задаём строгий generic тип контейнера.
|
|
||||||
typedef Registration<TContainer> = void Function(TContainer);
|
|
||||||
|
|
||||||
abstract class DIAdapter<TContainer> {
|
|
||||||
/// Устанавливает зависимости с помощью строго типизированного контейнера.
|
|
||||||
void setupDependencies(void Function(TContainer container) registration);
|
|
||||||
|
|
||||||
/// Возвращает типобезопасную функцию регистрации зависимостей под конкретный сценарий.
|
|
||||||
Registration<TContainer> universalRegistration<S extends Enum>({
|
|
||||||
required S scenario,
|
|
||||||
required int chainCount,
|
|
||||||
required int nestingDepth,
|
|
||||||
required UniversalBindingMode bindingMode,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Резолвит (возвращает) экземпляр типа [T] (по имени, если требуется).
|
|
||||||
T resolve<T extends Object>({String? named});
|
|
||||||
|
|
||||||
/// Асинхронно резолвит экземпляр типа [T] (если нужно).
|
|
||||||
Future<T> resolveAsync<T extends Object>({String? named});
|
|
||||||
|
|
||||||
/// Уничтожает/отчищает DI-контейнер.
|
|
||||||
void teardown();
|
|
||||||
|
|
||||||
/// Открывает дочерний scope и возвращает новый адаптер (если поддерживается).
|
|
||||||
DIAdapter<TContainer> openSubScope(String name);
|
|
||||||
|
|
||||||
/// Ожидание готовности DI контейнера (если нужно для async DI).
|
|
||||||
Future<void> waitForAsyncReady() async {}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import 'package:benchmark_di/scenarios/universal_binding_mode.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_scenario.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_service.dart';
|
|
||||||
import 'package:get_it/get_it.dart';
|
|
||||||
import 'di_adapter.dart';
|
|
||||||
|
|
||||||
/// Универсальный DIAdapter для GetIt c поддержкой scopes и строгой типизацией.
|
|
||||||
class GetItAdapter extends DIAdapter<GetIt> {
|
|
||||||
late GetIt _getIt;
|
|
||||||
final String? _scopeName;
|
|
||||||
final bool _isSubScope;
|
|
||||||
bool _scopePushed = false;
|
|
||||||
|
|
||||||
/// Основной (root) и subScope-конструкторы.
|
|
||||||
GetItAdapter({GetIt? instance, String? scopeName, bool isSubScope = false})
|
|
||||||
: _scopeName = scopeName,
|
|
||||||
_isSubScope = isSubScope {
|
|
||||||
if (instance != null) {
|
|
||||||
_getIt = instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setupDependencies(void Function(GetIt container) registration) {
|
|
||||||
if (_isSubScope) {
|
|
||||||
// Создаём scope через pushNewScope с init
|
|
||||||
_getIt.pushNewScope(
|
|
||||||
scopeName: _scopeName,
|
|
||||||
init: (getIt) => registration(getIt),
|
|
||||||
);
|
|
||||||
_scopePushed = true;
|
|
||||||
} else {
|
|
||||||
_getIt = GetIt.asNewInstance();
|
|
||||||
registration(_getIt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
T resolve<T extends Object>({String? named}) =>
|
|
||||||
_getIt<T>(instanceName: named);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<T> resolveAsync<T extends Object>({String? named}) async =>
|
|
||||||
_getIt<T>(instanceName: named);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void teardown() {
|
|
||||||
if (_isSubScope && _scopePushed) {
|
|
||||||
_getIt.popScope();
|
|
||||||
_scopePushed = false;
|
|
||||||
} else {
|
|
||||||
_getIt.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
GetItAdapter openSubScope(String name) =>
|
|
||||||
GetItAdapter(instance: _getIt, scopeName: name, isSubScope: true);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> waitForAsyncReady() async {
|
|
||||||
await _getIt.allReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Registration<GetIt> universalRegistration<S extends Enum>({
|
|
||||||
required S scenario,
|
|
||||||
required int chainCount,
|
|
||||||
required int nestingDepth,
|
|
||||||
required UniversalBindingMode bindingMode,
|
|
||||||
}) {
|
|
||||||
if (scenario is UniversalScenario) {
|
|
||||||
return (getIt) {
|
|
||||||
switch (scenario) {
|
|
||||||
case UniversalScenario.asyncChain:
|
|
||||||
for (int chain = 1; chain <= chainCount; chain++) {
|
|
||||||
for (int level = 1; level <= nestingDepth; level++) {
|
|
||||||
final prevDepName = '${chain}_${level - 1}';
|
|
||||||
final depName = '${chain}_$level';
|
|
||||||
getIt.registerSingletonAsync<UniversalService>(
|
|
||||||
() async {
|
|
||||||
final prev = level > 1
|
|
||||||
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
|
|
||||||
: null;
|
|
||||||
return UniversalServiceImpl(value: depName, dependency: prev);
|
|
||||||
},
|
|
||||||
instanceName: depName,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UniversalScenario.register:
|
|
||||||
getIt.registerSingleton<UniversalService>(UniversalServiceImpl(value: 'reg', dependency: null));
|
|
||||||
break;
|
|
||||||
case UniversalScenario.named:
|
|
||||||
getIt.registerFactory<UniversalService>(() => UniversalServiceImpl(value: 'impl1'), instanceName: 'impl1');
|
|
||||||
getIt.registerFactory<UniversalService>(() => UniversalServiceImpl(value: 'impl2'), instanceName: 'impl2');
|
|
||||||
break;
|
|
||||||
case UniversalScenario.chain:
|
|
||||||
for (int chain = 1; chain <= chainCount; chain++) {
|
|
||||||
for (int level = 1; level <= nestingDepth; level++) {
|
|
||||||
final prevDepName = '${chain}_${level - 1}';
|
|
||||||
final depName = '${chain}_$level';
|
|
||||||
switch (bindingMode) {
|
|
||||||
case UniversalBindingMode.singletonStrategy:
|
|
||||||
getIt.registerSingleton<UniversalService>(
|
|
||||||
UniversalServiceImpl(
|
|
||||||
value: depName,
|
|
||||||
dependency: level > 1
|
|
||||||
? getIt<UniversalService>(instanceName: prevDepName)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
instanceName: depName,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case UniversalBindingMode.factoryStrategy:
|
|
||||||
getIt.registerFactory<UniversalService>(
|
|
||||||
() => UniversalServiceImpl(
|
|
||||||
value: depName,
|
|
||||||
dependency: level > 1
|
|
||||||
? getIt<UniversalService>(instanceName: prevDepName)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
instanceName: depName,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case UniversalBindingMode.asyncStrategy:
|
|
||||||
getIt.registerSingletonAsync<UniversalService>(
|
|
||||||
() async => UniversalServiceImpl(
|
|
||||||
value: depName,
|
|
||||||
dependency: level > 1
|
|
||||||
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
instanceName: depName,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UniversalScenario.override:
|
|
||||||
// handled at benchmark level
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (scenario == UniversalScenario.chain || scenario == UniversalScenario.override) {
|
|
||||||
final depName = '${chainCount}_$nestingDepth';
|
|
||||||
getIt.registerSingleton<UniversalService>(
|
|
||||||
getIt<UniversalService>(instanceName: depName),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw UnsupportedError('Scenario $scenario not supported by GetItAdapter');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
import 'package:benchmark_di/scenarios/universal_binding_mode.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_scenario.dart';
|
|
||||||
import 'package:benchmark_di/scenarios/universal_service.dart';
|
|
||||||
import 'package:riverpod/riverpod.dart' as rp;
|
|
||||||
import 'di_adapter.dart';
|
|
||||||
|
|
||||||
/// Унифицированный DIAdapter для Riverpod с поддержкой scopes и строгой типизацией.
|
|
||||||
class RiverpodAdapter extends DIAdapter<Map<String, rp.ProviderBase<Object?>>> {
|
|
||||||
rp.ProviderContainer? _container;
|
|
||||||
final Map<String, rp.ProviderBase<Object?>> _namedProviders;
|
|
||||||
final rp.ProviderContainer? _parent;
|
|
||||||
|
|
||||||
RiverpodAdapter({
|
|
||||||
rp.ProviderContainer? container,
|
|
||||||
Map<String, rp.ProviderBase<Object?>>? providers,
|
|
||||||
rp.ProviderContainer? parent,
|
|
||||||
bool isSubScope = false,
|
|
||||||
}) : _container = container,
|
|
||||||
_namedProviders = providers ?? <String, rp.ProviderBase<Object?>>{},
|
|
||||||
_parent = parent;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setupDependencies(void Function(Map<String, rp.ProviderBase<Object?>> container) registration) {
|
|
||||||
_container ??= _parent == null
|
|
||||||
? rp.ProviderContainer()
|
|
||||||
: rp.ProviderContainer(parent: _parent);
|
|
||||||
registration(_namedProviders);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
T resolve<T extends Object>({String? named}) {
|
|
||||||
final key = named ?? T.toString();
|
|
||||||
final provider = _namedProviders[key];
|
|
||||||
if (provider == null) {
|
|
||||||
throw Exception('Provider not found for $key');
|
|
||||||
}
|
|
||||||
return _container!.read(provider) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<T> resolveAsync<T extends Object>({String? named}) async {
|
|
||||||
final key = named ?? T.toString();
|
|
||||||
final provider = _namedProviders[key];
|
|
||||||
if (provider == null) {
|
|
||||||
throw Exception('Provider not found for $key');
|
|
||||||
}
|
|
||||||
// Если это FutureProvider — используем .future
|
|
||||||
if (provider.runtimeType.toString().contains('FutureProvider')) {
|
|
||||||
return await _container!.read((provider as dynamic).future) as T;
|
|
||||||
}
|
|
||||||
return resolve<T>(named: named);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void teardown() {
|
|
||||||
_container?.dispose();
|
|
||||||
_container = null;
|
|
||||||
_namedProviders.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
RiverpodAdapter openSubScope(String name) {
|
|
||||||
final newContainer = rp.ProviderContainer(parent: _container);
|
|
||||||
return RiverpodAdapter(
|
|
||||||
container: newContainer,
|
|
||||||
providers: Map.of(_namedProviders),
|
|
||||||
parent: _container,
|
|
||||||
isSubScope: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> waitForAsyncReady() async {
|
|
||||||
// Riverpod синхронный по умолчанию.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Registration<Map<String, rp.ProviderBase<Object?>>> universalRegistration<S extends Enum>({
|
|
||||||
required S scenario,
|
|
||||||
required int chainCount,
|
|
||||||
required int nestingDepth,
|
|
||||||
required UniversalBindingMode bindingMode,
|
|
||||||
}) {
|
|
||||||
if (scenario is UniversalScenario) {
|
|
||||||
return (providers) {
|
|
||||||
switch (scenario) {
|
|
||||||
case UniversalScenario.register:
|
|
||||||
providers['UniversalService'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'reg', dependency: null));
|
|
||||||
break;
|
|
||||||
case UniversalScenario.named:
|
|
||||||
providers['impl1'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'impl1'));
|
|
||||||
providers['impl2'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'impl2'));
|
|
||||||
break;
|
|
||||||
case UniversalScenario.chain:
|
|
||||||
for (int chain = 1; chain <= chainCount; chain++) {
|
|
||||||
for (int level = 1; level <= nestingDepth; level++) {
|
|
||||||
final prevDepName = '${chain}_${level - 1}';
|
|
||||||
final depName = '${chain}_$level';
|
|
||||||
providers[depName] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(
|
|
||||||
value: depName,
|
|
||||||
dependency: level > 1 ? ref.watch(providers[prevDepName] as rp.ProviderBase<UniversalService>) : null,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final depName = '${chainCount}_$nestingDepth';
|
|
||||||
providers['UniversalService'] = rp.Provider<UniversalService>((ref) => ref.watch(providers[depName] as rp.ProviderBase<UniversalService>));
|
|
||||||
break;
|
|
||||||
case UniversalScenario.override:
|
|
||||||
// handled at benchmark level
|
|
||||||
break;
|
|
||||||
case UniversalScenario.asyncChain:
|
|
||||||
for (int chain = 1; chain <= chainCount; chain++) {
|
|
||||||
for (int level = 1; level <= nestingDepth; level++) {
|
|
||||||
final prevDepName = '${chain}_${level - 1}';
|
|
||||||
final depName = '${chain}_$level';
|
|
||||||
providers[depName] = rp.FutureProvider<UniversalService>((ref) async {
|
|
||||||
return UniversalServiceImpl(
|
|
||||||
value: depName,
|
|
||||||
dependency: level > 1
|
|
||||||
? await ref.watch((providers[prevDepName] as rp.FutureProvider<UniversalService>).future) as UniversalService?
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final depName = '${chainCount}_$nestingDepth';
|
|
||||||
providers['UniversalService'] = rp.FutureProvider<UniversalService>((ref) async {
|
|
||||||
return await ref.watch((providers[depName] as rp.FutureProvider<UniversalService>).future);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw UnsupportedError('Scenario $scenario not supported by RiverpodAdapter');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
/// Enum to represent the DI registration/binding mode.
|
|
||||||
enum UniversalBindingMode {
|
|
||||||
/// Singleton/provider binding.
|
|
||||||
singletonStrategy,
|
|
||||||
|
|
||||||
/// Factory-based binding.
|
|
||||||
factoryStrategy,
|
|
||||||
|
|
||||||
/// Async-based binding.
|
|
||||||
asyncStrategy,
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/// Enum to represent which scenario is constructed for the benchmark.
|
|
||||||
enum UniversalScenario {
|
|
||||||
/// Single registration.
|
|
||||||
register,
|
|
||||||
/// Chain of dependencies.
|
|
||||||
chain,
|
|
||||||
/// Named registrations.
|
|
||||||
named,
|
|
||||||
/// Child-scope override scenario.
|
|
||||||
override,
|
|
||||||
/// Asynchronous chain scenario.
|
|
||||||
asyncChain,
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
/// Base interface for any universal service in the benchmarks.
|
|
||||||
///
|
|
||||||
/// Represents an object in the dependency chain with an identifiable value
|
|
||||||
/// and (optionally) a dependency on a previous service in the chain.
|
|
||||||
abstract class UniversalService {
|
|
||||||
/// String ID for this service instance (e.g. chain/level info).
|
|
||||||
final String value;
|
|
||||||
/// Optional reference to dependency service in the chain.
|
|
||||||
final UniversalService? dependency;
|
|
||||||
UniversalService({required this.value, this.dependency});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default implementation for [UniversalService] used in service chains.
|
|
||||||
class UniversalServiceImpl extends UniversalService {
|
|
||||||
UniversalServiceImpl({required super.value, super.dependency});
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="WEB_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
|
||||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
# Generated by pub
|
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
|
||||||
packages:
|
|
||||||
ansi_modifier:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: ansi_modifier
|
|
||||||
sha256: "4b97c241f345e49c929bd56d0198b567b7dfcca7ec8d4f798745c9ced998684c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.4"
|
|
||||||
args:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: args
|
|
||||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.7.0"
|
|
||||||
async:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: async
|
|
||||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.13.0"
|
|
||||||
benchmark_harness:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: benchmark_harness
|
|
||||||
sha256: "83f65107165883ba8623eb822daacb23dcf9f795c66841de758c9dd7c5a0cf28"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.1"
|
|
||||||
benchmark_runner:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: benchmark_runner
|
|
||||||
sha256: "7de181228eb74cb34507ded2260fe88b3b71e0aacfe0dfa794df49edaf041ca3"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.0.4"
|
|
||||||
cherrypick:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "../cherrypick"
|
|
||||||
relative: true
|
|
||||||
source: path
|
|
||||||
version: "3.0.0-dev.5"
|
|
||||||
collection:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: collection
|
|
||||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.19.1"
|
|
||||||
exception_templates:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: exception_templates
|
|
||||||
sha256: "517f7c770da690073663f867ee2057ae2f4ffb28edae9da9faa624aa29ac76eb"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.1"
|
|
||||||
get_it:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: get_it
|
|
||||||
sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "8.2.0"
|
|
||||||
lazy_memo:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: lazy_memo
|
|
||||||
sha256: dcb30b4184a6d767e1d779d74ce784d752d38313b8fb4bad6b659ae7af4bb34d
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.2.3"
|
|
||||||
lints:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: lints
|
|
||||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.1.1"
|
|
||||||
meta:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: meta
|
|
||||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.17.0"
|
|
||||||
path:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path
|
|
||||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.9.1"
|
|
||||||
riverpod:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: riverpod
|
|
||||||
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.6.1"
|
|
||||||
stack_trace:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: stack_trace
|
|
||||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.12.1"
|
|
||||||
state_notifier:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: state_notifier
|
|
||||||
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
sdks:
|
|
||||||
dart: ">=3.6.0 <4.0.0"
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
name: benchmark_di
|
|
||||||
version: 0.1.0
|
|
||||||
publish_to: none
|
|
||||||
description: Universal benchmark for any DI library (cherrypick, get_it, and others)
|
|
||||||
|
|
||||||
environment:
|
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
cherrypick:
|
|
||||||
path: ../cherrypick
|
|
||||||
args: ^2.7.0
|
|
||||||
get_it: ^8.2.0
|
|
||||||
riverpod: ^2.6.1
|
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
lints: ^5.0.0
|
|
||||||
benchmark_harness: ^2.2.2
|
|
||||||
benchmark_runner: ^0.0.2
|
|
||||||
2
cherrypick/.gitignore
vendored
2
cherrypick/.gitignore
vendored
@@ -22,5 +22,3 @@ doc/api/
|
|||||||
|
|
||||||
# FVM Version Cache
|
# FVM Version Cache
|
||||||
.fvm/
|
.fvm/
|
||||||
|
|
||||||
pubspec_overrides.yaml
|
|
||||||
@@ -1,86 +1,3 @@
|
|||||||
## 3.0.0-dev.7
|
|
||||||
|
|
||||||
> Note: This release has breaking changes.
|
|
||||||
|
|
||||||
- **FIX**(comment): fix warnings.
|
|
||||||
- **FIX**(license): correct urls.
|
|
||||||
- **FEAT**: add Disposable interface source and usage example.
|
|
||||||
- **DOCS**(readme): add comprehensive section on annotations and DI code generation.
|
|
||||||
- **DOCS**(readme): add detailed section and examples for automatic Disposable resource cleanup\n\n- Added a dedicated section with English description and code samples on using Disposable for automatic resource management.\n- Updated Features to include automatic resource cleanup for Disposable dependencies.\n\nHelps developers understand and implement robust DI resource management practices.
|
|
||||||
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
|
||||||
- **DOCS**(faq): add best practice FAQ about using await with scope disposal.
|
|
||||||
- **BREAKING** **REFACTOR**(core): make closeRootScope async and await dispose.
|
|
||||||
- **BREAKING** **DOCS**(disposable): add detailed English documentation and usage examples for Disposable interface; chore: update binding_resolver and add explanatory comment in scope_test for deprecated usage.\n\n- Expanded Disposable interface docs, added sync & async example classes, and CherryPick integration sample.\n- Clarified how to implement and use Disposable in DI context.\n- Updated binding_resolver for internal improvements.\n- Added ignore for deprecated member use in scope_test for clarity and future upgrades.\n\nBREAKING CHANGE: Documentation style enhancement and clearer API usage for Disposable implementations.
|
|
||||||
|
|
||||||
## 3.0.0-dev.6
|
|
||||||
|
|
||||||
> Note: This release has breaking changes.
|
|
||||||
|
|
||||||
- **FIX**: improve global cycle detector logic.
|
|
||||||
- **DOCS**(readme): add comprehensive DI state and action logging to features.
|
|
||||||
- **DOCS**(helper): add complete DartDoc with real usage examples for CherryPick class.
|
|
||||||
- **DOCS**(log_format): add detailed English documentation for formatLogMessage function.
|
|
||||||
- **BREAKING** **FEAT**(core): refactor root scope API, improve logger injection, helpers, and tests.
|
|
||||||
- **BREAKING** **FEAT**(logger): add extensible logging API, usage examples, and bilingual documentation.
|
|
||||||
|
|
||||||
## 3.0.0-dev.5
|
|
||||||
|
|
||||||
- **REFACTOR**(scope): simplify _findBindingResolver<T> with one-liner and optional chaining.
|
|
||||||
- **PERF**(scope): speed up dependency lookup with Map-based binding resolver index.
|
|
||||||
- **DOCS**(perf): clarify Map-based resolver optimization applies since v3.0.0 in all docs.
|
|
||||||
- **DOCS**: update EN/RU quick start and tutorial with Fast Map-based lookup section; clarify performance benefit in README.
|
|
||||||
|
|
||||||
## 3.0.0-dev.4
|
|
||||||
|
|
||||||
- **REFACTOR**(scope): simplify _findBindingResolver<T> with one-liner and optional chaining.
|
|
||||||
- **PERF**(scope): speed up dependency lookup with Map-based binding resolver index.
|
|
||||||
- **DOCS**(perf): clarify Map-based resolver optimization applies since v3.0.0 in all docs.
|
|
||||||
- **DOCS**: update EN/RU quick start and tutorial with Fast Map-based lookup section; clarify performance benefit in README.
|
|
||||||
|
|
||||||
## 3.0.0-dev.3
|
|
||||||
|
|
||||||
- **REFACTOR**(scope): simplify _findBindingResolver<T> with one-liner and optional chaining.
|
|
||||||
- **PERF**(scope): speed up dependency lookup with Map-based binding resolver index.
|
|
||||||
- **DOCS**(perf): clarify Map-based resolver optimization applies since v3.0.0 in all docs.
|
|
||||||
- **DOCS**: update EN/RU quick start and tutorial with Fast Map-based lookup section; clarify performance benefit in README.
|
|
||||||
|
|
||||||
## 3.0.0-dev.2
|
|
||||||
|
|
||||||
> Note: This release has breaking changes.
|
|
||||||
|
|
||||||
- **FEAT**(binding): add deprecated proxy async methods for backward compatibility and highlight transition to modern API.
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
- **BREAKING** **FEAT**: implement comprehensive circular dependency detection system.
|
|
||||||
- **BREAKING** **FEAT**: implement comprehensive circular dependency detection system.
|
|
||||||
|
|
||||||
## 3.0.0-dev.1
|
|
||||||
|
|
||||||
- **DOCS**: add quick guide for circular dependency detection to README.
|
|
||||||
|
|
||||||
## 3.0.0-dev.0
|
|
||||||
|
|
||||||
> Note: This release has breaking changes.
|
|
||||||
|
|
||||||
- **BREAKING** **FEAT**: implement comprehensive circular dependency detection system.
|
|
||||||
|
|
||||||
## 2.2.0
|
|
||||||
|
|
||||||
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
|
||||||
|
|
||||||
## 2.2.0-dev.1
|
|
||||||
|
|
||||||
- **FIX**: fix warnings.
|
|
||||||
|
|
||||||
## 2.2.0-dev.0
|
|
||||||
|
|
||||||
- **FEAT**: Add async dependency resolution and enhance example.
|
|
||||||
- **FEAT**: implement toInstanceAync binding.
|
|
||||||
|
|
||||||
## 2.1.0
|
|
||||||
|
|
||||||
- Graduate package to a stable release. See pre-releases prior to this version for changelog entries.
|
|
||||||
|
|
||||||
## 2.1.0-dev.1
|
## 2.1.0-dev.1
|
||||||
|
|
||||||
- **FIX**: fix warnings.
|
- **FIX**: fix warnings.
|
||||||
|
|||||||
@@ -192,7 +192,7 @@
|
|||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
https://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -1,443 +1,89 @@
|
|||||||
# CherryPick
|
# CherryPick Flutter
|
||||||
|
|
||||||
`cherrypick` is a flexible and lightweight dependency injection library for Dart and Flutter.
|
`cherrypick_flutter` is a robust Flutter library designed for managing and accessing dependencies using a scope context provided by `CherryPickProvider`. It enhances your application's modularity and testability by simplifying dependency injection.
|
||||||
It provides an easy-to-use system for registering, scoping, and resolving dependencies using modular bindings and hierarchical scopes. The design enables cleaner architecture, testability, and modular code in your applications.
|
|
||||||
|
|
||||||
---
|
## Quick Start
|
||||||
|
|
||||||
## Table of Contents
|
### Core Components of Dependency Injection (DI)
|
||||||
- [Key Features](#key-features)
|
|
||||||
- [Installation](#installation)
|
|
||||||
- [Getting Started](#getting-started)
|
|
||||||
- [Core Concepts](#core-concepts)
|
|
||||||
- [Binding](#binding)
|
|
||||||
- [Module](#module)
|
|
||||||
- [Scope](#scope)
|
|
||||||
- [Automatic Resource Cleanup with Disposable](#automatic-resource-cleanup-with-disposable)
|
|
||||||
- [Dependency Resolution API](#dependency-resolution-api)
|
|
||||||
- [Using Annotations & Code Generation](#using-annotations--code-generation)
|
|
||||||
- [Advanced Features](#advanced-features)
|
|
||||||
- [Hierarchical Subscopes](#hierarchical-subscopes)
|
|
||||||
- [Logging](#logging)
|
|
||||||
- [Circular Dependency Detection](#circular-dependency-detection)
|
|
||||||
- [Performance Improvements](#performance-improvements)
|
|
||||||
- [Example Application](#example-application)
|
|
||||||
- [FAQ](#faq)
|
|
||||||
- [Documentation Links](#documentation-links)
|
|
||||||
- [Contributing](#contributing)
|
|
||||||
- [License](#license)
|
|
||||||
|
|
||||||
---
|
#### Binding
|
||||||
|
|
||||||
## Key Features
|
A Binding is a custom instance configurator crucial for setting up dependencies. It offers the following key methods:
|
||||||
- Main Scope and Named Subscopes
|
|
||||||
- Named Instance Binding and Resolution
|
|
||||||
- Asynchronous and Synchronous Providers
|
|
||||||
- Providers Supporting Runtime Parameters
|
|
||||||
- Singleton Lifecycle Management
|
|
||||||
- Modular and Hierarchical Composition
|
|
||||||
- Null-safe Resolution (tryResolve/tryResolveAsync)
|
|
||||||
- Circular Dependency Detection (Local and Global)
|
|
||||||
- Comprehensive logging of dependency injection state and actions
|
|
||||||
- Automatic resource cleanup for all registered Disposable dependencies
|
|
||||||
|
|
||||||
---
|
- `toInstance()`: Directly provides an initialized instance.
|
||||||
|
- `toProvide()`: Accepts a provider function for lazy initialization.
|
||||||
|
- `toProvideAsync()`: Accepts an asynchronous provider for lazy initialization.
|
||||||
|
- `toProvideWithParams()`: Accepts a provider function requiring dynamic parameters.
|
||||||
|
- `toProvideAsyncWithParams()`: Accepts an asynchronous provider requiring dynamic parameters.
|
||||||
|
- `withName()`: Assigns a name for instance retrieval by name.
|
||||||
|
- `singleton()`: Marks the instance as a singleton, ensuring only one instance exists within the scope.
|
||||||
|
|
||||||
## Installation
|
##### Example:
|
||||||
|
|
||||||
Add to your `pubspec.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
dependencies:
|
|
||||||
cherrypick: ^<latest_version>
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
dart pub get
|
|
||||||
```
|
|
||||||
---
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
Here is a minimal example that registers and resolves a dependency:
|
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
// Direct instance initialization using toInstance()
|
||||||
|
Binding<String>().toInstance("hello world");
|
||||||
|
|
||||||
|
// Lazy initialization via provider
|
||||||
|
Binding<String>().toProvide(() => "hello world");
|
||||||
|
|
||||||
class AppModule extends Module {
|
// Asynchronous lazy initialization
|
||||||
@override
|
Binding<String>().toProvideAsync(() async => "hello async world");
|
||||||
void builder(Scope currentScope) {
|
|
||||||
bind<ApiClient>().toInstance(ApiClientMock());
|
|
||||||
bind<String>().toProvide(() => "Hello, CherryPick!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final rootScope = CherryPick.openRootScope();
|
/ Asynchronous lazy initialization with dynamic parameters
|
||||||
rootScope.installModules([AppModule()]);
|
Binding<String>().toProvideAsyncWithParams((params) async => "hello $params");
|
||||||
|
|
||||||
final greeting = rootScope.resolve<String>();
|
// Initialization with dynamic parameters
|
||||||
print(greeting); // prints: Hello, CherryPick!
|
Binding<String>().toProvideWithParams((params) => "hello $params");
|
||||||
|
|
||||||
await CherryPick.closeRootScope();
|
// Named instance for resolution
|
||||||
|
Binding<String>().toProvide(() => "hello world").withName("my_string").toInstance("hello world");
|
||||||
|
|
||||||
|
// Singleton instance
|
||||||
|
Binding<String>().toProvide(() => "hello world").singleton();
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
#### Module
|
||||||
|
|
||||||
## Core Concepts
|
A Module encapsulates bindings, logically organizing dependencies. Implement the `void builder(Scope currentScope)` method to create a custom module.
|
||||||
|
|
||||||
### Binding
|
##### Example:
|
||||||
|
|
||||||
A **Binding** acts as a configuration for how to create or provide a particular dependency. Bindings support:
|
|
||||||
|
|
||||||
- Direct instance assignment (`toInstance()`, `toInstanceAsync()`)
|
|
||||||
- Lazy providers (sync/async functions)
|
|
||||||
- Provider functions supporting dynamic parameters
|
|
||||||
- Named instances for resolving by string key
|
|
||||||
- Optional singleton lifecycle
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
```dart
|
|
||||||
// Provide a direct instance
|
|
||||||
Binding<String>().toInstance("Hello world");
|
|
||||||
|
|
||||||
// Provide an async direct instance
|
|
||||||
Binding<String>().toInstanceAsync(Future.value("Hello world"));
|
|
||||||
|
|
||||||
// Provide a lazy sync instance using a factory
|
|
||||||
Binding<String>().toProvide(() => "Hello world");
|
|
||||||
|
|
||||||
// Provide a lazy async instance using a factory
|
|
||||||
Binding<String>().toProvideAsync(() async => "Hello async world");
|
|
||||||
|
|
||||||
// Provide an instance with dynamic parameters (sync)
|
|
||||||
Binding<String>().toProvideWithParams((params) => "Hello $params");
|
|
||||||
|
|
||||||
// Provide an instance with dynamic parameters (async)
|
|
||||||
Binding<String>().toProvideAsyncWithParams((params) async => "Hello $params");
|
|
||||||
|
|
||||||
// Named instance for retrieval by name
|
|
||||||
Binding<String>().toProvide(() => "Hello world").withName("my_string");
|
|
||||||
|
|
||||||
// Mark as singleton (only one instance within the scope)
|
|
||||||
Binding<String>().toProvide(() => "Hello world").singleton();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Module
|
|
||||||
|
|
||||||
A **Module** is a logical collection point for bindings, designed for grouping and initializing related dependencies. Implement the `builder` method to define how dependencies should be bound within the scope.
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
class AppModule extends Module {
|
class AppModule extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<ApiClient>().toInstance(ApiClientMock());
|
bind<ApiClient>().toInstance(ApiClientMock());
|
||||||
bind<String>().toProvide(() => "Hello world!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Scope
|
#### Scope
|
||||||
|
|
||||||
A **Scope** manages a tree of modules and dependency instances. Scopes can be nested into hierarchies (parent-child), supporting modular app composition and context-specific overrides.
|
A Scope manages your dependency tree, holding modules and instances. Use the scope to access dependencies with `resolve<T>()` or `resolveAsync<T>()` for asynchronous operations.
|
||||||
|
|
||||||
You typically work with the root scope, but can also create named subscopes as needed.
|
##### Example:
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
// Open the main/root scope
|
// Open the main scope
|
||||||
final rootScope = CherryPick.openRootScope();
|
final rootScope = CherryPick.openRootScope();
|
||||||
|
|
||||||
// Install a custom module
|
// Install custom modules
|
||||||
rootScope.installModules([AppModule()]);
|
rootScope.installModules([AppModule()]);
|
||||||
|
|
||||||
// Resolve a dependency synchronously
|
// Resolve an instance
|
||||||
final str = rootScope.resolve<String>();
|
final str = rootScope.resolve<String>();
|
||||||
|
|
||||||
// Resolve a dependency asynchronously
|
// Asynchronously resolve an instance
|
||||||
final result = await rootScope.resolveAsync<String>();
|
final asyncStr = await rootScope.resolveAsync<String>();
|
||||||
|
|
||||||
// Recommended: Close the root scope and release all resources
|
// Close the main scope
|
||||||
await CherryPick.closeRootScope();
|
CherryPick.closeRootScope();
|
||||||
|
|
||||||
// Alternatively, you may manually call dispose on any scope you manage individually
|
|
||||||
// await rootScope.dispose();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Automatic Resource Cleanup with Disposable
|
|
||||||
|
|
||||||
CherryPick can automatically clean up any dependency that implements the `Disposable` interface. This makes resource management (for controllers, streams, sockets, files, etc.) easy and reliable—especially when scopes or the app are shut down.
|
|
||||||
|
|
||||||
If you bind an object implementing `Disposable` as a singleton or provide it via the DI container, CherryPick will call its `dispose()` method when the scope is closed or cleaned up.
|
|
||||||
|
|
||||||
#### Key Points
|
|
||||||
- Supports both synchronous and asynchronous cleanup (dispose may return `void` or `Future`).
|
|
||||||
- All `Disposable` instances from the current scope and subscopes will be disposed in the correct order.
|
|
||||||
- Prevents resource leaks and enforces robust cleanup.
|
|
||||||
- No manual wiring needed once your class implements `Disposable`.
|
|
||||||
|
|
||||||
#### Minimal Sync Example
|
|
||||||
```dart
|
|
||||||
class CacheManager implements Disposable {
|
|
||||||
void dispose() {
|
|
||||||
cache.clear();
|
|
||||||
print('CacheManager disposed!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final scope = CherryPick.openRootScope();
|
|
||||||
scope.installModules([
|
|
||||||
Module((bind) => bind<CacheManager>().toProvide(() => CacheManager()).singleton()),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// ...later
|
|
||||||
await CherryPick.closeRootScope(); // prints: CacheManager disposed!
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Async Example
|
|
||||||
```dart
|
|
||||||
class MyServiceWithSocket implements Disposable {
|
|
||||||
@override
|
|
||||||
Future<void> dispose() async {
|
|
||||||
await socket.close();
|
|
||||||
print('Socket closed!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.installModules([
|
|
||||||
Module((bind) => bind<MyServiceWithSocket>().toProvide(() => MyServiceWithSocket()).singleton()),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await CherryPick.closeRootScope(); // awaits async disposal
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tip:** Always call `await CherryPick.closeRootScope()` or `await scope.closeSubScope(key)` in your shutdown/teardown logic to ensure all resources are released automatically.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Automatic resource management (`Disposable`, `dispose`)
|
|
||||||
|
|
||||||
CherryPick automatically manages the lifecycle of any object registered via DI that implements the `Disposable` interface.
|
|
||||||
|
|
||||||
**Best practice:**
|
|
||||||
Always finish your work with `await CherryPick.closeRootScope()` (for the root scope) or `await scope.closeSubScope('key')` (for subscopes).
|
|
||||||
These methods will automatically await `dispose()` on all resolved objects (e.g., singletons) that implement `Disposable`, ensuring proper and complete resource cleanup—sync or async.
|
|
||||||
|
|
||||||
Manual `await scope.dispose()` may be useful if you manually manage custom scopes.
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
```dart
|
|
||||||
class MyService implements Disposable {
|
|
||||||
@override
|
|
||||||
FutureOr<void> dispose() async {
|
|
||||||
// release resources, close streams, perform async shutdown, etc.
|
|
||||||
print('MyService disposed!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final scope = openRootScope();
|
|
||||||
scope.installModules([
|
|
||||||
ModuleImpl(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
final service = scope.resolve<MyService>();
|
|
||||||
|
|
||||||
// ... use service
|
|
||||||
|
|
||||||
// Recommended completion:
|
|
||||||
await CherryPick.closeRootScope(); // will print: MyService disposed!
|
|
||||||
|
|
||||||
// Or, to close and clean up a subscope and its resources:
|
|
||||||
await scope.closeSubScope('feature');
|
|
||||||
|
|
||||||
class ModuleImpl extends Module {
|
|
||||||
@override
|
|
||||||
void builder(Scope scope) {
|
|
||||||
bind<MyService>().toProvide(() => MyService()).singleton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Working with Subscopes
|
|
||||||
|
|
||||||
```dart
|
|
||||||
// Open a named child scope (e.g., for a feature/module)
|
|
||||||
final subScope = rootScope.openSubScope('featureScope')
|
|
||||||
..installModules([FeatureModule()]);
|
|
||||||
|
|
||||||
// Resolve from subScope, with fallback to parents if missing
|
|
||||||
final dataBloc = await subScope.resolveAsync<DataBloc>();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fast Dependency Lookup (Performance Improvement)
|
|
||||||
|
|
||||||
> **Performance Note:**
|
|
||||||
> **Starting from version 3.0.0**, CherryPick uses a Map-based resolver index for dependency lookup. This means calls to `resolve<T>()` and related methods are now O(1) operations, regardless of the number of modules or bindings in your scope. Previously, the library had to iterate over all modules and bindings to locate the requested dependency, which could impact performance as your project grew.
|
|
||||||
>
|
|
||||||
> This optimization is internal and does not change any library APIs or usage patterns, but it significantly improves resolution speed in larger applications.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Using Annotations & Code Generation
|
|
||||||
|
|
||||||
CherryPick provides best-in-class developer ergonomics and type safety through **Dart annotations** and code generation. This lets you dramatically reduce boilerplate: simply annotate your classes, fields, and modules, run the code generator, and enjoy auto-wired dependency injection!
|
|
||||||
|
|
||||||
### How It Works
|
|
||||||
|
|
||||||
1. **Annotate** your services, providers, and fields using `cherrypick_annotations`.
|
|
||||||
2. **Generate** code using `cherrypick_generator` with `build_runner`.
|
|
||||||
3. **Use** generated modules and mixins for fully automated DI (dependency injection).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Supported Annotations
|
|
||||||
|
|
||||||
| Annotation | Target | Description |
|
|
||||||
|-------------------|---------------|--------------------------------------------------------------------------------|
|
|
||||||
| `@injectable()` | class | Enables automatic field injection for this class (mixin will be generated) |
|
|
||||||
| `@inject()` | field | Field will be injected using DI (works with @injectable classes) |
|
|
||||||
| `@module()` | class | Declares a DI module; its methods can provide services/providers |
|
|
||||||
| `@provide` | method | Registers as a DI provider method (may have dependencies as parameters) |
|
|
||||||
| `@instance` | method/class | Registers an instance (new object on each resolution, i.e. factory) |
|
|
||||||
| `@singleton` | method/class | Registers as a singleton (one instance per scope) |
|
|
||||||
| `@named` | field/param | Use named instance (bind/resolve by name or apply to field/param) |
|
|
||||||
| `@scope` | field/param | Inject or resolve from a specific named scope |
|
|
||||||
| `@params` | param | Marks method parameter as filled by user-supplied runtime params at resolution |
|
|
||||||
|
|
||||||
You can easily **combine** these annotations for advanced scenarios!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Field Injection Example
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:cherrypick_annotations/cherrypick_annotations.dart';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
class ProfilePage with _\$ProfilePage {
|
|
||||||
@inject()
|
|
||||||
late final AuthService auth;
|
|
||||||
|
|
||||||
@inject()
|
|
||||||
@scope('profile')
|
|
||||||
late final ProfileManager manager;
|
|
||||||
|
|
||||||
@inject()
|
|
||||||
@named('admin')
|
|
||||||
late final UserService adminUserService;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- After running build_runner, the mixin `_ | |||||||