Для быстрого ознакомления:
Что такое Native Assets - объяснение для новичков
История и эволюция
Архитектура системы
Build Hooks - сердце системы
Практические примеры
Продвинутые концепции
Лучшие практики
Troubleshooting и отладка
Экосистема и дальнейшее развитие
Реальные кейсы использования
Производительность и оптимизация
Что такое Native Assets - объяснение для новичков
Простыми словами
Представьте, что у вас есть Dart-программа, и вы хотите использовать готовую библиотеку, написанную на C, C++, Rust или другом языке. Раньше это было сложно - нужно было вручную компилировать библиотеку, следить за тем, чтобы она попала в нужное место, и писать много дополнительного кода.
Native Assets - это система, которая автоматизирует весь этот процесс. Она позволяет вашему Dart-пакету "включать в себя" нативный код и автоматически его компилировать и подключать.
Аналогия из жизни
Это как заказ еды с доставкой:
Раньше: Вы сами покупали продукты, готовили, мыли посуду.
С Native Assets: Вы просто говорите "хочу пиццу", а система сама заказывает, доставляет и даже убирает за собой.
Техническое определение
Native Assets — это официальная система в Dart, которая позволяет пакетам содержать не только Dart-код, но и исходный код на других языках (C, C++, Rust и др.). Система автоматически управляет сборкой этого нативного кода в динамические библиотеки (.so
, .dll
, .dylib
), их упаковкой в приложение и обеспечивает прозрачный доступ к ним из Dart-кода во время выполнения через FFI (Foreign Function Interface).
История и эволюция
До Native Assets (темные времена)
Разработчик хочет использовать C-библиотеку:
Вручную компилирует библиотеку для каждой платформы.
Копирует
.so
/.dll
/.dylib
файлы в правильные места.Пишет FFI-биндинги.
Молится, чтобы все работало на разных устройствах.
Повторяет все это при каждом обновлении.
С Native Assets (светлое будущее)
Разработчик хочет использовать C-библиотеку:
Добавляет зависимость в
pubspec.yaml
.Описывает процесс сборки в
hook/build.dart
(один раз).Система автоматически все компилирует и подключает.
Работает везде "из коробки".
Timeline развития
Dart 3.2: Появилась первая версия Native Assets за экспериментальным флагом
--enable-experiment=native-assets
.Dart 3.4 (Май 2024): Стабилизация Native Assets. Функция стала доступна по умолчанию, флаг больше не требуется.
Flutter 3.22 (Май 2024): Интеграция Native Assets во Flutter. Система стала официально поддерживаться для сборки приложений на всех платформах.
Архитектура системы
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Dart Package │───▶│ Build Hooks │───▶│ Native Assets │
│ │ │ │ │ │
│ - pubspec.yaml │ │ - hook/build.dart│ │ - .so/.dll/.dylib│
│ - lib/*.dart │ │ │ │ - metadata.json │
│ - src/*.c │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
-
Package Structure
my_native_package/ ├── pubspec.yaml # Зависимости и конфигурация пакета ├── lib/ │ └── my_package.dart # Dart API для взаимодействия с нативным кодом ├── src/ │ ├── my_lib.c # Нативный исходный код │ └── my_lib.h # Заголовочные файлы └── hook/ └── build.dart # Скрипт-инструкция по сборке нативного кода
-
Build System Flow
dart pub get
илиflutter build
обнаруживает наличиеhook/build.dart
.Запускает этот скрипт (build hook) в изолированной среде для каждой целевой платформы.
Hook компилирует нативный код, используя системные компиляторы (GCC, Clang, MSVC) или специализированные инструменты (Cargo, CMake).
Hook генерирует метаданные о созданных ассетах (библиотеках).
Система сборки упаковывает эти ассеты вместе с приложением.
Dart-код может использовать FFI для вызова нативных функций, при этом загрузка библиотеки происходит автоматически.
Build Hooks - сердце системы
Что такое Build Hook
Build Hook — это специальный Dart-скрипт (hook/build.dart
), который говорит системе сборки:
Какой нативный код нужно скомпилировать.
Как именно его компилировать (какие флаги, зависимости).
Где разместить результат.
Простой пример hook/build.dart (для демонстрации)
Важно: Этот пример использует прямой вызов gcc
и не является кроссплатформенным. Он показан только для понимания принципа. В реальных проектах всегда используйте обертки, такие как native_toolchain_c
.
// hook/build.dart
import 'package:native_assets_cli/native_assets_cli.dart';
import 'dart:io';
void main(List<String> args) async {
await build(args, (config, output) async {
// Определяем, что мы хотим скомпилировать
final packageName = config.packageName;
final sourceFile = config.packageRoot.resolve('src/my_lib.c');
final outDir = config.outputDirectory;
// Имя библиотеки зависит от платформы
String libName;
if (config.targetOS == OS.windows) {
libName = 'my_lib.dll';
} else if (config.targetOS == OS.macOS) {
libName = 'libmy_lib.dylib';
} else {
libName = 'libmy_lib.so';
}
final outFile = outDir.resolve(libName);
// Компилируем C код в динамическую библиотеку
final result = await Process.run(
'gcc',
[
'-shared',
'-fPIC',
'-o',
outFile.toFilePath(),
sourceFile.toFilePath(),
],
);
if (result.exitCode != 0) {
throw Exception('Compilation failed: ${result.stderr}');
}
// Сообщаем системе о созданной библиотеке
output.addAsset(NativeCodeAsset(
package: packageName,
name: 'src/my_lib.c', // Имя ассета должно соответствовать пути к исходнику
file: outFile,
linkMode: LinkMode.dynamic,
os: config.targetOS,
architecture: config.targetArchitecture,
));
});
}
Продвинутый и рекомендуемый пример с native_toolchain_c
Этот пакет предоставляет удобный CBuilder
для кроссплатформенной компиляции.
// hook/build.dart
import 'package:native_assets_cli/native_assets_cli.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';
void main(List<String> args) async {
await build(args, (config, output) async {
final cbuilder = CBuilder.library(
name: 'my_complex_lib',
assetName: 'path/to/my_lib.dart', // Путь к Dart файлу, который будет использовать библиотеку
sources: [
'src/core.c',
'src/utils.c',
],
includes: [
'src/include/',
],
defines: {
'VERSION': '1.0.0',
'DEBUG': config.buildMode == BuildMode.debug ? '1' : '0',
},
);
await cbuilder.run(
buildConfig: config,
buildOutput: output,
// Для отладки можно добавить логгер
// logger: Logger()..level = Level.all,
);
});
}
Практические примеры
Пример 1: Простая математическая библиотека
Структура проекта
math_native/
├── pubspec.yaml
├── lib/
│ └── math_native.dart
├── src/
│ ├── math_ops.c
│ └── math_ops.h
└── hook/
└── build.dart
C-код (src/math_ops.c
) (C нет в редакторе хабра в выпадающем списке языков, поставил С++)
// src/math_ops.c
#include "math_ops.h"
// Простой итеративный Фибоначчи
int fibonacci(int n) {
if (n <= 1) return n;
int a = 0, b = 1, c;
for (int i = 2; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return b;
}
Заголовочный файл (src/math_ops.h
)
// src/math_ops.h
#ifndef MATH_OPS_H
#define MATH_OPS_H
// Dart FFI требует явного указания видимости для Windows
#if defined(_WIN32)
#define API __declspec(dllexport)
#else
#define API
#endif
API int fibonacci(int n);
#endif
Build Hook (hook/build.dart
)
// hook/build.dart
// Примечание: для этого кода нужно добавить пакет native_toolchain_rust
import 'package.native_assets_cli/native_assets_cli.dart';
import 'package.native_toolchain_rust/native_toolchain_rust.dart';
void main(List<String> args) async {
await build(args, (config, output) async {
// Создаем сборщик для Rust-библиотеки
final rustBuilder = RustBuilder.library(
name: 'string_processor', // Имя вашего пакета из файла Cargo.toml
// Указываем Dart-файл, который будет использовать эту библиотеку
assetName: 'package:имя_вашего_пакета/имя_dart_файла.dart',
);
// Запускаем сборку
await rustBuilder.run(
buildConfig: config,
buildOutput: output,
);
});
}
Dart API (lib/math_native.dart
)
// lib/math_native.dart
import 'dart:ffi';
// Аннотация @Native указывает FFI на имя нативной функции.
// Система сборки Native Assets автоматически найдет и загрузит
// нужную библиотеку, скомпилированную для текущей платформы.
@Native<Int32 Function(Int32)>('fibonacci')
external int _fibonacci(int n);
/// Вычисление числа Фибоначчи с использованием нативной реализации.
int fibonacci(int n) => _fibonacci(n);
pubspec.yaml
name: math_native
description: Fast native math operations.
version: 1.0.0
publish_to: 'none' # Для локального примера
environment:
sdk: '>=3.4.0 <4.0.0'
dependencies:
ffi: ^2.1.0
# Пакеты для сборки теперь не нужно указывать,
# Dart SDK находит и использует их автоматически.
# dev_dependencies:
# native_assets_cli: ...
# native_toolchain_c: ...
Пример 2: Интеграция с Rust
Rust-код (src/
lib.rs
)
// src/lib.rs
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[no_mangle]
pub extern "C" fn process_string(input: *const c_char) -> *mut c_char {
let c_str = unsafe { CStr::from_ptr(input) };
let rust_string = c_str.to_string_lossy();
// Обработка строки в Rust (реверс и верхний регистр)
let processed = rust_string.chars().rev().collect::<String>().to_uppercase();
let c_string = CString::new(processed).unwrap();
c_string.into_raw()
}
#[no_mangle]
pub extern "C" fn free_string(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
// Восстанавливаем CString из указателя и освобождаем память
let _ = CString::from_raw(ptr);
};
}
}
Cargo.toml
Ini, TOML (TOML нет в редакторе хабра в выпадающем списке языков)
[package]
name = "string_processor"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
Build Hook для Rust (hook/build.dart
)
// hook/build.dart
import 'dart:io';
import 'package:native_assets_cli/native_assets_cli.dart';
void main(List<String> args) async {
await build(args, (config, output) async {
// Вызов Cargo для сборки Rust проекта
final result = await Process.run(
'cargo',
['build', '--release'],
workingDirectory: config.packageRoot.toFilePath(),
);
if (result.exitCode != 0) {
throw Exception('Rust build failed: ${result.stderr}');
}
// Находим путь к скомпилированной библиотеке
// ... логика поиска .dll/.so/.dylib в target/release/ ...
// Для простоты примера, опустим эту часть
// Добавляем ассет в вывод (в реальном коде нужно найти файл)
// output.addAsset(...)
});
}
// Примечание: для Rust существуют более удобные пакеты-обертки,
// такие как `native_toolchain_rust`, которые автоматизируют этот процесс.
Продвинутые концепции
1. Условная компиляция
Build hook может анализировать конфигурацию сборки (config
) и включать разные исходники или флаги.
// hook/build.dart
void main(List<String> args) async {
await build(args, (config, output) async {
final sources = <String>['src/core.c'];
final defines = <String, String>{};
// Платформо-зависимый код
switch (config.targetOS) {
case OS.windows:
sources.add('src/platform/windows.c');
defines['PLATFORM_WINDOWS'] = '1';
break;
case OS.linux:
sources.add('src/platform/linux.c');
defines['PLATFORM_LINUX'] = '1';
break;
case OS.macOS:
sources.add('src/platform/macos.c');
defines['PLATFORM_MACOS'] = '1';
break;
default:
// Обработка неподдерживаемых платформ
}
final cbuilder = CBuilder.library(
name: 'cross_platform_lib',
assetName: 'package:my_package/my_package.dart',
sources: sources,
defines: defines,
);
await cbuilder.run(buildConfig: config, buildOutput: output);
});
}
2. Управление зависимостями
В build hook можно скачивать и компилировать сторонние библиотеки.
// hook/build.dart
// (Концептуальный пример)
void main(List<String> args) async {
await build(args, (config, output) async {
final depsDir = config.outputDirectory.resolve('deps');
// Скачиваем и распаковываем зависимость (например, libjpeg)
if (!await Directory.fromUri(depsDir).exists()) {
await downloadAndExtract(
url: 'https://example.com/libjpeg.tar.gz',
destination: depsDir,
);
// Запускаем ./configure && make для сборки зависимости
await buildDependency(depsDir);
}
final cbuilder = CBuilder.library(
name: 'my_image_lib',
assetName: 'package:my_package/my_package.dart',
sources: ['src/image_utils.c'],
includes: [
'src/',
depsDir.resolve('include').path,
],
// Линкуемся со статически собранной зависимостью
libraries: [
depsDir.resolve('lib/libjpeg.a').path,
],
);
await cbuilder.run(buildConfig: config, buildOutput: output);
});
}
Вспомогательные функции downloadAndExtract
и buildDependency
должны быть реализованы отдельно.
Лучшие практики
1. Структура проекта
Хорошо организованный проект облегчает поддержку.
my_native_package/
├── pubspec.yaml
├── README.md
├── lib/
│ ├── my_package.dart # Публичный Dart API
│ └── src/
│ ├── bindings.dart # FFI-биндинги (@Native)
│ └── exceptions.dart # Кастомные исключения
├── src/ # Нативный код
│ ├── core/
│ │ ├── api.h # Публичный C API
│ │ └── api.c
│ └── platform/ # Платформо-специфичный код
└── hook/
└── build.dart # Build hook
2. Обработка ошибок в Build Hooks
Build hook должен быть надежным и предоставлять понятные сообщения об ошибках.
// hook/build.dart
void main(List<String> args) async {
await build(args, (config, output) async {
try {
// Проверяем наличие необходимых инструментов (например, CMake)
await _ensureToolExists('cmake');
final cbuilder = CBuilder.library(...);
await cbuilder.run(
buildConfig: config,
buildOutput: output,
logger: Logger.root, // Включаем логирование
);
} on ToolNotFoundException catch (e) {
print('Ошибка сборки: ${e.message}');
print('Рекомендация: Установите ${e.toolName} и добавьте в PATH.');
rethrow;
} catch (e, stackTrace) {
print('Непредвиденная ошибка во время сборки: $e');
print('Стек вызовов: $stackTrace');
rethrow;
}
});
}
// Вспомогательная функция _ensureToolExists и исключение ToolNotFoundException
3. Кроссплатформенная совместимость в Dart
Правильный подход: С Native Assets вам не нужно писать код для загрузки библиотек под разные платформы в Dart. Эта логика полностью находится в hook/build.dart
. Dart-код остается чистым и платформо-независимым.
Устаревший подход (НЕ ИСПОЛЬЗОВАТЬ С NATIVE ASSETS):
// НЕПРАВИЛЬНО: Ручная загрузка библиотеки
if (Platform.isWindows) {
DynamicLibrary.open('my_lib.dll');
} // ... и т.д.
Правильный Dart-код:
// lib/src/bindings.dart
import 'dart:ffi';
// Этот код будет работать на Windows, macOS, Linux, Android и iOS
// без каких-либо изменений.
@Native<Int32 Function(Int32)>('my_function')
external int _myFunction(int value);
class MyLib {
static int myFunction(int value) {
try {
return _myFunction(value);
} catch (e) {
// Можно обернуть ошибку FFI в кастомное исключение
throw NativeException('Failed to call my_function: $e');
}
}
}
class NativeException implements Exception {
final String message;
NativeException(this.message);
}
4. Тестирование Native Assets
Тесты должны проверять как успешное выполнение, так и обработку ошибок.
// test/native_test.dart
import 'package:test/test.dart';
import 'package:math_native/math_native.dart'; // Импортируем наш пакет
void main() {
group('Native Fibonacci Tests', () {
test('should return correct values for base cases', () {
expect(fibonacci(0), 0);
expect(fibonacci(1), 1);
});
test('should calculate fibonacci correctly for a small number', () {
expect(fibonacci(10), 55);
});
// Можно добавить тесты на производительность
test('should execute within reasonable time', () {
final stopwatch = Stopwatch()..start();
fibonacci(40); // Достаточно большая нагрузка
stopwatch.stop();
expect(stopwatch.elapsedMilliseconds, lessThan(100));
});
});
}
Troubleshooting и отладка
Частые проблемы и решения
-
Сборка падает с непонятной ошибкой
Решение: Запустите сборку с максимальной детализацией логов:
flutter build <platform> -v
. Ищите ошибки от компилятора (Clang, GCC, MSVC) или от вашего build hook.
-
UnsatisfiedLinkError
илиLookup failed
Причина: Dart FFI не может найти нативную функцию.
-
Решение:
Проверьте, что имя функции в аннотации
@Native<...>
('my_function'
) точно совпадает с именем в нативном коде.Для C++ убедитесь, что функция обернута в
extern "C"
.Для Windows убедитесь, что функция экспортируется с помощью
__declspec(dllexport)
.Проверьте
assetName
вCBuilder.library()
вhook/build.dart
. Он должен указывать на Dart-файл, где используется@Native
.
-
Build Hook не выполняется
-
Проверьте:
Файл точно называется
build.dart
и находится в папкеhook
в корне пакета.Выполните
dart pub get
илиflutter clean
иflutter pub get
, чтобы система сборки заново обнаружила hook (горячая перезагрузка здесь не работает).
-
Экосистема и дальнейшее развитие
Текущие возможности (не будущее)
Декларативные биндинги: Аннотация
@Native<...>
является основным способом связывания с нативным кодом.Интеграция с Flutter: Native Assets полностью поддерживаются во Flutter для сборки на Android, iOS, Windows, macOS и Linux.
Интеграция с IDE: IDE (VS Code, Android Studio) предоставляют базовую поддержку, включая подсветку синтаксиса и возможность отладки Dart-кода. Прямой переход к нативному коду пока ограничен.
Сторонние инструменты: Сообщество активно развивает пакеты-обертки для популярных систем сборки (
native_toolchain_c
,native_toolchain_cmake
,native_toolchain_rust
), которые значительно упрощают написание build-хуков.
Планируемые улучшения
Генерация биндингов: Инструменты, такие как
package:ffigen
, адаптируются для работы с Native Assets, что позволит автоматически генерировать Dart-код из C/C++ заголовочных файлов.Улучшенная отладка: Работа над возможностью бесшовной отладки с переходом между Dart и нативным кодом.
Поддержка WebAssembly (Wasm): Интеграция с Wasm позволит использовать тот же нативный код (скомпилированный в Wasm) в веб-приложениях.
Реальные кейсы использования
Кейс 1: Высокопроизводительная обработка изображений
Проблема: Приложение для обработки фотографий работало медленно из-за операций над пикселями в Dart-коде.
Решение с Native Assets: Вынести ресурсоемкие алгоритмы (фильтры, размытие) в C/C++ и вызывать их через FFI.
Dart-код (c корректным FFI):
// lib/image_processor.dart
import 'dart:ffi';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
@Native<Void Function(Pointer<Uint8>, Int32, Int32, Double)>('apply_sepia_filter')
external void _applySepiaFilter(Pointer<Uint8> imageData, int width, int height, double intensity);
void applySepiaFilter(Uint8List imageData, int width, int height, {double intensity = 1.0}) {
// Выделяем память в нативной куче
final pointer = calloc<Uint8>(imageData.length);
// Копируем данные из Dart-массива в нативную память
pointer.asTypedList(imageData.length).setAll(0, imageData);
try {
// Вызываем быструю нативную функцию
_applySepiaFilter(pointer, width, height, intensity);
// Копируем результат обратно в Dart-массив
imageData.setAll(0, pointer.asTypedList(imageData.length));
} finally {
// Обязательно освобождаем память
calloc.free(pointer);
}
}
Нативный код для фильтра apply_sepia_filter
пишется на C.
Результат: Обработка изображений становится в теории в 10-15 раз быстрее, но у меня ускорение в пределах от 3 до 8 раз, если кто-то в курсе что я делаю не так - пожалуйста прокоментируйте.
Кейс 2: Интеграция с существующей C++ библиотекой
Проблема: Есть готовая, протестированная C++ библиотека для работы с 3D-геометрией, которую нужно использовать в Dart-приложении.
Решение: Создать тонкую C-обертку (wrapper
) для C++ кода и подключить ее через Native Assets.
Обертка C API (src/geometry_wrapper.cpp
)
#include "geometry_lib.hpp" // Существующая C++ библиотека
// extern "C" отключает C++ name mangling, делая функции видимыми для C FFI
extern "C" {
API double calculate_distance(Point3D* p1, Point3D* p2) {
// Кастуем указатели к C++ типам и вызываем C++ код
auto* point1 = reinterpret_cast<geometry::Point3D*>(p1);
auto* point2 = reinterpret_cast<geometry::Point3D*>(p2);
return geometry::distance(*point1, *point2);
}
}
Build hook для этого будет компилировать C++ файлы с помощью g++
или clang++
.
Производительность и оптимизация
Benchmarking Native Assets
Используйте package:benchmark_harness
для сравнения производительности Dart и нативной реализации.
// benchmark/performance_test.dart
import 'package:benchmark_harness/benchmark_harness.dart';
import 'package:my_native_package/my_native_package.dart'; // Наш пакет
// Benchmark для нативной реализации
class NativeBenchmark extends BenchmarkBase {
NativeBenchmark() : super('MatrixMultiplication.Native');
@override
void run() => nativeMatrixMultiply(); // вызов нативной функции
}
// Benchmark для Dart-реализации
class DartBenchmark extends BenchmarkBase {
DartBenchmark() : super('MatrixMultiplication.Dart');
@override
void run() => dartMatrixMultiply(); // вызов Dart-функции
}
void main() {
NativeBenchmark().report();
DartBenchmark().report();
}
Оптимизация Build Hooks
Для ускорения сборки и повышения производительности нативного кода:
Используйте флаги оптимизации: В
hook/build.dart
для релизных сборок добавляйте флаги-O3
(максимальная оптимизация) и-flto
(Link Time Optimization).Кэширование: Реализуйте в build hook логику кэширования. Если исходные файлы не изменились, используйте уже скомпилированные артефакты вместо повторной сборки.
Платформо-специфичные флаги: Используйте флаги, оптимизированные для конкретных архитектур, например
-march=native
.Проверьте
assetName
вCBuilder.library()
в файлеhook/build.dart
. Он должен точно указывать на тот Dart-файл, где используется аннотация@Native
. Если вы используете@Native
в файлеlib/src/bindings.dart
, то иassetName
должен быть'package:имя_пакета/src/bindings.dart'
.
// hook/build.dart
// Функция для получения флагов компилятора C
List<String> _getOptimizedCFlags(BuildMode buildMode) {
if (buildMode == BuildMode.release) {
return ['-O3', '-DNDEBUG']; // Оптимизация и отключение assert'ов
}
return ['-g', '-DDEBUG']; // Флаги для отладки
}
// Функция для получения флагов компоновщика
List<String> _getOptimizedLdFlags(BuildMode buildMode) {
if (buildMode == BuildMode.release) {
return ['-flto']; // Оптимизация на этапе компоновки
}
return [];
}
void main(List<String> args) async {
await build(args, (config, output) async {
final cbuilder = CBuilder.library(
name: 'my_optimized_lib',
assetName: 'package:my_package/my_package.dart',
sources: ['src/my_lib.c'],
// Правильная передача флагов
cFlags: _getOptimizedCFlags(config.buildMode),
ldFlags: _getOptimizedLdFlags(config.buildMode),
);
await cbuilder.run(buildConfig: config, buildOutput: output);
});
}
Заключение
Dart Native Assets представляют собой революционное решение для интеграции нативного кода. Эта система решает множество проблем, с которыми сталкивались разработчики при работе с FFI, и является стабильным, мощным инструментом.
Ключевые преимущества
Автоматизация: Полностью автоматизированный процесс компиляции и подключения нативных библиотек.
Кроссплатформенность: Единый build-скрипт для сборки под все целевые платформы (мобильные, десктопные).
Производительность: Прямые вызовы нативных функций без накладных расходов, присущих Method Channels.
Простота: Значительно упрощенный процесс разработки и поддержки пакетов с нативным кодом.
На 2025 год система Native Assets является зрелой и рекомендованной для всех задач, требующих высокой производительности или интеграции с существующими C/C++/Rust библиотеками в экосистеме Dart и Flutter.
Комментарии (4)
dv0ich
15.06.2025 15:06(по мотивам заглавной пикчи)
Знакомьтесь, друзья: это Сишка-тян. Очень простая на вид и при должном подходе способна стать верным помощником, который выполняет кучу задач за день, но имеет некоторые особенности. Так, за её буфера ни в коем случае нельзя браться наощупь, это действие всегда требует чёткого визуального контроля, иначе велик шанс промахнуться мимо буферов, а от этого Сишка-тян моментально звереет и может проломить тебе голову каким-нибудь тяжёлым предметом или прострелить тебе ногу (или себе). Сишка-тян предпочитает утилитарные профессии, ей только дай покопаться в механике или электрике, но даже к её любимой работе её необходимо должным образом подготовить, иначе она всё разнесёт к чертям. Злые языки утверждают, что Сишка-тян - не кто иной, как Асм-кун, прошедший через пластическую операцию, но сама она это категорически отрицает.
А это - Плюсо-тян. Близкая родственница Сишки-тян, но если та почти ничего за душой не имеет, то Плюсо-тян довольно состоятельна, владеет кучей имущества, как явно полезного, так и весьма странного. В целом, жизнь с ней куда проще и легче, чем с Сишкой-тян (можно браться за буфера наугад, не промахнёшься), но упаси боже тебя небрежно обращаться с её имуществом или с ней самой - тогда в ней просыпаются гены Сишки-тян и её поведение становится непредсказуемым вплоть до самых печальных последствий. По каким-то причинам имущество Плюсо-тян периодически увеличивается стараниями каких-то мутных дедов, но на вопросы почему и зачем это происходит Плюсо-тян отвечает загадочным молчанием.
Alex283
"Кто бы мог думать, ваше превосходительство, что человеческая пища, в первоначальном виде, летает, плавает и на деревьях растет?" М.Е.Салтыков-Щедрин