From 590b876cf45599b06b563d63d2cefb9aa0b1901d Mon Sep 17 00:00:00 2001 From: Sergey Penkovsky Date: Thu, 7 Aug 2025 13:12:56 +0300 Subject: [PATCH] feat: add Riverpod adapter, async-chain support via FutureProvider, full DI CLI/bench integration, benchmarking, ascii performance graphs in markdown --- benchmark_di/lib/cli/benchmark_cli.dart | 7 +- benchmark_di/lib/cli/parser.dart | 2 +- .../lib/di_adapters/riverpod_adapter.dart | 72 +++++++++++++++++++ .../scenarios/di_universal_registration.dart | 63 ++++++++++++++-- .../lib/scenarios/universal_chain_module.dart | 4 +- benchmark_di/pubspec.lock | 32 +++++++++ benchmark_di/pubspec.yaml | 1 + 7 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 benchmark_di/lib/di_adapters/riverpod_adapter.dart diff --git a/benchmark_di/lib/cli/benchmark_cli.dart b/benchmark_di/lib/cli/benchmark_cli.dart index a904a81..20dba80 100644 --- a/benchmark_di/lib/cli/benchmark_cli.dart +++ b/benchmark_di/lib/cli/benchmark_cli.dart @@ -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/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. /// @@ -29,7 +30,11 @@ class BenchmarkCliRunner { for (final c in config.chainCounts) { for (final d in config.nestDepths) { BenchmarkResult benchResult; - final di = config.di == 'getit' ? GetItAdapter() : CherrypickDIAdapter(); + final di = config.di == 'getit' + ? GetItAdapter() + : config.di == 'riverpod' + ? RiverpodAdapter() + : CherrypickDIAdapter(); if (scenario == UniversalScenario.asyncChain) { final benchAsync = UniversalChainAsyncBenchmark(di, chainCount: c, nestingDepth: d, mode: mode, diff --git a/benchmark_di/lib/cli/parser.dart b/benchmark_di/lib/cli/parser.dart index 27d0719..8321bf1 100644 --- a/benchmark_di/lib/cli/parser.dart +++ b/benchmark_di/lib/cli/parser.dart @@ -104,7 +104,7 @@ BenchmarkCliConfig parseBenchmarkCli(List args) { ..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 or getit') + ..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) { diff --git a/benchmark_di/lib/di_adapters/riverpod_adapter.dart b/benchmark_di/lib/di_adapters/riverpod_adapter.dart new file mode 100644 index 0000000..93e82e1 --- /dev/null +++ b/benchmark_di/lib/di_adapters/riverpod_adapter.dart @@ -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> _namedProviders; + final ProviderContainer? _parent; + + // Основной конструктор + RiverpodAdapter() : _parent = null { + _namedProviders = >{}; + } + + // Внутренний конструктор для дочерних скоупов + 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({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 resolveAsync({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(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 waitForAsyncReady() async { + // Riverpod синхронный по умолчанию. + return; + } +} diff --git a/benchmark_di/lib/scenarios/di_universal_registration.dart b/benchmark_di/lib/scenarios/di_universal_registration.dart index 0cd2d14..e25cae8 100644 --- a/benchmark_di/lib/scenarios/di_universal_registration.dart +++ b/benchmark_di/lib/scenarios/di_universal_registration.dart @@ -4,9 +4,7 @@ import '../di_adapters/di_adapter.dart'; import '../di_adapters/cherrypick_adapter.dart'; import '../di_adapters/get_it_adapter.dart'; import 'universal_chain_module.dart'; -import 'universal_service.dart'; -import 'package:get_it/get_it.dart'; -import 'package:cherrypick/cherrypick.dart'; +import 'package:riverpod/riverpod.dart' as rp; /// Возвращает универсальную функцию регистрации зависимостей, /// подходящую под выбранный DI-адаптер. @@ -41,7 +39,7 @@ void Function(dynamic) getUniversalRegistration( final prev = level > 1 ? await getIt.getAsync(instanceName: prevDepName) : null; - return UniversalServiceImpl(value: depName, dependency: prev); + return UniversalServiceImpl(value: depName, dependency: prev as UniversalService?); }, instanceName: depName, ); @@ -112,5 +110,62 @@ void Function(dynamic) getUniversalRegistration( } }; } + + // Riverpod + if (adapter.runtimeType.toString().contains('RiverpodAdapter')) { + // Регистрация Provider-ов по универсальному сценарию + return (providers) { + // providers это Map> + switch (scenario) { + case UniversalScenario.register: + providers['UniversalService'] = rp.Provider((ref) => UniversalServiceImpl(value: 'reg', dependency: null)); + break; + case UniversalScenario.named: + providers['impl1'] = rp.Provider((ref) => UniversalServiceImpl(value: 'impl1')); + providers['impl2'] = rp.Provider((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((ref) => UniversalServiceImpl( + value: depName, + dependency: level > 1 ? ref.watch(providers[prevDepName] as rp.ProviderBase) : null, + )); + } + } + // Alias для последнего (универсальное имя) + final depName = '${chainCount}_$nestingDepth'; + providers['UniversalService'] = rp.Provider((ref) => ref.watch(providers[depName] as rp.ProviderBase)); + 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((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((ref) async { + return await ref.watch(providers[depName]!.future) as UniversalService; + }); + break; + } + }; + } + throw UnsupportedError('Unknown DIAdapter type: ${adapter.runtimeType}'); } diff --git a/benchmark_di/lib/scenarios/universal_chain_module.dart b/benchmark_di/lib/scenarios/universal_chain_module.dart index b4657c1..e7e3380 100644 --- a/benchmark_di/lib/scenarios/universal_chain_module.dart +++ b/benchmark_di/lib/scenarios/universal_chain_module.dart @@ -127,14 +127,14 @@ class UniversalChainModule extends Module { } } // Регистрация алиаса без имени (на последний элемент цепочки) - final depName = '${chainCount}_${nestingDepth}'; + final depName = '${chainCount}_$nestingDepth'; bind() .toProvide(() => currentScope.resolve(named: depName)) .singleton(); break; case UniversalScenario.override: // handled at benchmark level, но алиас нужен прямо в этом scope! - final depName = '${chainCount}_${nestingDepth}'; + final depName = '${chainCount}_$nestingDepth'; bind() .toProvide(() => currentScope.resolve(named: depName)) .singleton(); diff --git a/benchmark_di/pubspec.lock b/benchmark_di/pubspec.lock index 24877a8..11216c2 100644 --- a/benchmark_di/pubspec.lock +++ b/benchmark_di/pubspec.lock @@ -96,5 +96,37 @@ packages: 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" diff --git a/benchmark_di/pubspec.yaml b/benchmark_di/pubspec.yaml index 525f529..87d2865 100644 --- a/benchmark_di/pubspec.yaml +++ b/benchmark_di/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: path: ../cherrypick args: ^2.7.0 get_it: ^8.2.0 + riverpod: ^2.6.1 dev_dependencies: lints: ^5.0.0