mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-24 05:25:19 +00:00
feat: add Riverpod adapter, async-chain support via FutureProvider, full DI CLI/bench integration, benchmarking, ascii performance graphs in markdown
This commit is contained in:
@@ -12,6 +12,7 @@ import 'package:benchmark_di/benchmarks/universal_chain_benchmark.dart';
|
|||||||
import 'package:benchmark_di/benchmarks/universal_chain_async_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/cherrypick_adapter.dart';
|
||||||
import 'package:benchmark_di/di_adapters/get_it_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.
|
/// Command-line interface (CLI) runner for benchmarks.
|
||||||
///
|
///
|
||||||
@@ -29,7 +30,11 @@ class BenchmarkCliRunner {
|
|||||||
for (final c in config.chainCounts) {
|
for (final c in config.chainCounts) {
|
||||||
for (final d in config.nestDepths) {
|
for (final d in config.nestDepths) {
|
||||||
BenchmarkResult benchResult;
|
BenchmarkResult benchResult;
|
||||||
final di = config.di == 'getit' ? GetItAdapter() : CherrypickDIAdapter();
|
final di = config.di == 'getit'
|
||||||
|
? GetItAdapter()
|
||||||
|
: config.di == 'riverpod'
|
||||||
|
? RiverpodAdapter()
|
||||||
|
: CherrypickDIAdapter();
|
||||||
if (scenario == UniversalScenario.asyncChain) {
|
if (scenario == UniversalScenario.asyncChain) {
|
||||||
final benchAsync = UniversalChainAsyncBenchmark(di,
|
final benchAsync = UniversalChainAsyncBenchmark(di,
|
||||||
chainCount: c, nestingDepth: d, mode: mode,
|
chainCount: c, nestingDepth: d, mode: mode,
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ BenchmarkCliConfig parseBenchmarkCli(List<String> args) {
|
|||||||
..addOption('repeat', abbr: 'r', defaultsTo: '2')
|
..addOption('repeat', abbr: 'r', defaultsTo: '2')
|
||||||
..addOption('warmup', abbr: 'w', defaultsTo: '1')
|
..addOption('warmup', abbr: 'w', defaultsTo: '1')
|
||||||
..addOption('format', abbr: 'f', defaultsTo: 'pretty')
|
..addOption('format', abbr: 'f', defaultsTo: 'pretty')
|
||||||
..addOption('di', defaultsTo: 'cherrypick', help: 'DI implementation: cherrypick or getit')
|
..addOption('di', defaultsTo: 'cherrypick', help: 'DI implementation: cherrypick, getit or riverpod')
|
||||||
..addFlag('help', abbr: 'h', negatable: false, help: 'Show help');
|
..addFlag('help', abbr: 'h', negatable: false, help: 'Show help');
|
||||||
final result = parser.parse(args);
|
final result = parser.parse(args);
|
||||||
if (result['help'] == true) {
|
if (result['help'] == true) {
|
||||||
|
|||||||
72
benchmark_di/lib/di_adapters/riverpod_adapter.dart
Normal file
72
benchmark_di/lib/di_adapters/riverpod_adapter.dart
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import 'package:riverpod/riverpod.dart';
|
||||||
|
import 'di_adapter.dart';
|
||||||
|
|
||||||
|
/// RiverpodAdapter реализует DIAdapter для универсального бенчмарка через Riverpod.
|
||||||
|
class RiverpodAdapter implements DIAdapter {
|
||||||
|
late ProviderContainer _container;
|
||||||
|
late final Map<String?, ProviderBase<Object?>> _namedProviders;
|
||||||
|
final ProviderContainer? _parent;
|
||||||
|
|
||||||
|
// Основной конструктор
|
||||||
|
RiverpodAdapter() : _parent = null {
|
||||||
|
_namedProviders = <String?, ProviderBase<Object?>>{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Внутренний конструктор для дочерних скоупов
|
||||||
|
RiverpodAdapter._child(this._container, this._namedProviders, this._parent);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setupDependencies(void Function(dynamic container) registration) {
|
||||||
|
// Для главного контейнера
|
||||||
|
_container = _parent == null
|
||||||
|
? ProviderContainer()
|
||||||
|
: ProviderContainer(parent: _parent);
|
||||||
|
registration(_namedProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Регистрировать провайдеры нужно по имени-сервису.
|
||||||
|
/// Пример: container['SomeClass'] = Provider((ref) => SomeClass());
|
||||||
|
|
||||||
|
@override
|
||||||
|
T resolve<T extends Object>({String? named}) {
|
||||||
|
final provider = _namedProviders[named ?? T.toString()];
|
||||||
|
if (provider == null) {
|
||||||
|
throw Exception('Provider not found for $named');
|
||||||
|
}
|
||||||
|
return _container.read(provider) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<T> resolveAsync<T extends Object>({String? named}) async {
|
||||||
|
final provider = _namedProviders[named ?? T.toString()];
|
||||||
|
if (provider == null) {
|
||||||
|
throw Exception('Provider not found for $named');
|
||||||
|
}
|
||||||
|
// Если это FutureProvider — используем .future
|
||||||
|
if (provider.runtimeType.toString().contains('FutureProvider')) {
|
||||||
|
final result = await _container.read((provider as dynamic).future);
|
||||||
|
return result as T;
|
||||||
|
}
|
||||||
|
return resolve<T>(named: named);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void teardown() {
|
||||||
|
_container.dispose();
|
||||||
|
_namedProviders.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DIAdapter openSubScope(String name) {
|
||||||
|
// Создаём дочерний scope через новый контейнер с parent
|
||||||
|
final childContainer = ProviderContainer(parent: _container);
|
||||||
|
// Провайдеры будут унаследованы (immutable копия), но при желании можно их расширять в дочернем scope.
|
||||||
|
return RiverpodAdapter._child(childContainer, Map.of(_namedProviders), _container);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> waitForAsyncReady() async {
|
||||||
|
// Riverpod синхронный по умолчанию.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,7 @@ import '../di_adapters/di_adapter.dart';
|
|||||||
import '../di_adapters/cherrypick_adapter.dart';
|
import '../di_adapters/cherrypick_adapter.dart';
|
||||||
import '../di_adapters/get_it_adapter.dart';
|
import '../di_adapters/get_it_adapter.dart';
|
||||||
import 'universal_chain_module.dart';
|
import 'universal_chain_module.dart';
|
||||||
import 'universal_service.dart';
|
import 'package:riverpod/riverpod.dart' as rp;
|
||||||
import 'package:get_it/get_it.dart';
|
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
|
||||||
|
|
||||||
/// Возвращает универсальную функцию регистрации зависимостей,
|
/// Возвращает универсальную функцию регистрации зависимостей,
|
||||||
/// подходящую под выбранный DI-адаптер.
|
/// подходящую под выбранный DI-адаптер.
|
||||||
@@ -41,7 +39,7 @@ void Function(dynamic) getUniversalRegistration(
|
|||||||
final prev = level > 1
|
final prev = level > 1
|
||||||
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
|
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
|
||||||
: null;
|
: null;
|
||||||
return UniversalServiceImpl(value: depName, dependency: prev);
|
return UniversalServiceImpl(value: depName, dependency: prev as UniversalService?);
|
||||||
},
|
},
|
||||||
instanceName: depName,
|
instanceName: depName,
|
||||||
);
|
);
|
||||||
@@ -112,5 +110,62 @@ void Function(dynamic) getUniversalRegistration(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Riverpod
|
||||||
|
if (adapter.runtimeType.toString().contains('RiverpodAdapter')) {
|
||||||
|
// Регистрация Provider-ов по универсальному сценарию
|
||||||
|
return (providers) {
|
||||||
|
// providers это Map<String, ProviderBase<Object?>>
|
||||||
|
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,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Alias для последнего (универсальное имя)
|
||||||
|
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]!.future) as UniversalService?
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Alias для последнего (универсальное имя)
|
||||||
|
final depName = '${chainCount}_$nestingDepth';
|
||||||
|
providers['UniversalService'] = rp.FutureProvider<UniversalService>((ref) async {
|
||||||
|
return await ref.watch(providers[depName]!.future) as UniversalService;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
throw UnsupportedError('Unknown DIAdapter type: ${adapter.runtimeType}');
|
throw UnsupportedError('Unknown DIAdapter type: ${adapter.runtimeType}');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,14 +127,14 @@ class UniversalChainModule extends Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Регистрация алиаса без имени (на последний элемент цепочки)
|
// Регистрация алиаса без имени (на последний элемент цепочки)
|
||||||
final depName = '${chainCount}_${nestingDepth}';
|
final depName = '${chainCount}_$nestingDepth';
|
||||||
bind<UniversalService>()
|
bind<UniversalService>()
|
||||||
.toProvide(() => currentScope.resolve<UniversalService>(named: depName))
|
.toProvide(() => currentScope.resolve<UniversalService>(named: depName))
|
||||||
.singleton();
|
.singleton();
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.override:
|
case UniversalScenario.override:
|
||||||
// handled at benchmark level, но алиас нужен прямо в этом scope!
|
// handled at benchmark level, но алиас нужен прямо в этом scope!
|
||||||
final depName = '${chainCount}_${nestingDepth}';
|
final depName = '${chainCount}_$nestingDepth';
|
||||||
bind<UniversalService>()
|
bind<UniversalService>()
|
||||||
.toProvide(() => currentScope.resolve<UniversalService>(named: depName))
|
.toProvide(() => currentScope.resolve<UniversalService>(named: depName))
|
||||||
.singleton();
|
.singleton();
|
||||||
|
|||||||
@@ -96,5 +96,37 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
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:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.6.0 <4.0.0"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ dependencies:
|
|||||||
path: ../cherrypick
|
path: ../cherrypick
|
||||||
args: ^2.7.0
|
args: ^2.7.0
|
||||||
get_it: ^8.2.0
|
get_it: ^8.2.0
|
||||||
|
riverpod: ^2.6.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^5.0.0
|
lints: ^5.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user