DatabaseSystem (Unity)
Unity-реализация драйверов базы данных, пресеты, атрибуты.
Назначение
Платформенная адаптация Database: загрузка пресетов из Resources / Addressables, ScriptableObject-пресеты записей, атрибут для type-safe выбора записей в Inspector, настройки Addressable-меток.
- Два драйвера:
ResourcesDriverиAddressablesDriver DatabaseDriverBase— общая логика (DRY)RecordPreset<T>— ScriptableObject-шаблон для записейDbRecordAttribute— фильтрация записей по типу/режиму в InspectorDatabaseSettings— конфигурация Addressable-меток- Partial-расширение
Record— добавлениеIcon(Sprite)
Вне ответственности: логика хранения, доступ по GUID, сохранение — это Core (Layer 1).
Зависимости
Vortex.Core.DatabaseSystem—Database,Record,IDriver,RecordTypesVortex.Core.LoaderSystem—Loader,IProcessVortex.Core.SettingsSystem—Settings,SettingsModelVortex.Core.Extensions—Crypto.GetNewGuid(),AddNew()Cysharp.Threading.Tasks—UniTaskSirenix.OdinInspector— редакторские атрибутыUnityEngine.AddressableAssets(опционально, заENABLE_ADDRESSABLES)
Драйверы
DatabaseDriverBase
Internal static класс — общая логика для обоих драйверов.
DatabaseDriverBase
├── _recordsLink — Dictionary<string, Record> (ссылка на реестр Database)
├── _multiInstanceRecordsLink — HashSet<string> (ссылка на MultiInstance GUID)
├── _resourcesIndex — Dictionary<string, IRecordPreset> (кеш пресетов)
├── SetIndex() — получение ссылок на реестры
├── PutData(IRecordPreset) — добавление пресета в кеш + запись в реестр
├── AddRecord() — маршрутизация: Singleton → _recordsLink, MultiInstance → _multiInstanceRecordsLink
├── GetNewRecord<T>(guid) — создание экземпляра из пресета
├── GetNewRecords<T>() — все MultiInstance копии по типу
├── CheckPresetType<T>(guid) — проверка соответствия типа
└── Clean() — очистка всех кешей
ResourcesDriver
Загрузка из Resources/Database/.
DatabaseDriver (Singleton<DatabaseDriver>, IDriver, IProcess)
├── DatabaseDriver.cs — IDriver API, делегирование к DatabaseDriverBase
├── DatabaseDriverExtLoadingSystem.cs — IProcess: Register, RunAsync
└── DatabaseDriverExtEditor.cs — IDriverEditor: ReloadDatabase, GetPresetForRecord
Загрузка:
[RuntimeInitializeOnLoadMethod]→Database.SetDriver(Instance)+Loader.Register(Instance)RunAsync()→Resources.LoadAll("Database")→ фильтрация поIRecordPreset→PutData()для каждогоCallOnInit()→Database.OnInit
WaitingFor: Type.EmptyTypes — нет зависимостей.
AddressablesDriver
Загрузка через Addressables API по меткам.
Структура аналогична ResourcesDriver. Код за #if ENABLE_ADDRESSABLES.
Загрузка:
[RuntimeInitializeOnLoadMethod]→Database.SetDriver(Instance)+Loader.Register(Instance)RunAsync()→ чтение меток изSettings.Data().DatabaseLabels- Для каждой метки:
Addressables.LoadAssetsAsync<IRecordPreset>(label, null) - Все загруженные пресеты →
PutData() CallOnInit()→Database.OnInitfinally→Addressables.Release(handle)для каждого handle
Требования:
- Пакет
com.unity.addressables(defineENABLE_ADDRESSABLESустанавливается автоматически черезDefinitionManager) - Метки указаны в
DatabaseSettings.databaseLabels - Пустой массив меток — лог ошибки, загрузка не выполняется
RecordPreset<T>
Abstract ScriptableObject — шаблон данных для записей.
RecordPreset<T> (SoData, IRecordPreset) where T : Record, new()
├── type — RecordTypes (Singleton / MultiInstance)
├── guid — string (Crypto.GetNewGuid())
├── nameRecord — string (auto-rename файла при изменении)
├── description — string
├── icon — Sprite
├── GetData() — new T() + CopyFrom(this)
├── CheckRecordType<TU>() / CheckRecordType(Type)
├── ResetGuid() — [Editor] генерация нового GUID
└── OnNameChanged() — [Editor] переименование ассета
IRecordPreset
interface IRecordPreset
{
RecordTypes RecordType { get; }
string GuidPreset { get; }
string Name { get; }
Record GetData();
bool CheckRecordType<TU>() where TU : Record;
bool CheckRecordType(Type type);
}
Record (partial, Unity)
Partial-расширение Core-класса Record, добавляет:
public Sprite Icon { get; protected set; }
DbRecordAttribute
Атрибут для string-полей — type-safe picker записей в Inspector.
[DbRecord] // все записи
[DbRecord(typeof(ProductRecord))] // по типу
[DbRecord(RecordTypes.Singleton)] // по режиму
[DbRecord(typeof(TemplateRecord), RecordTypes.MultiInstance)] // тип + режим
Свойства:
RecordClass— тип записи (default:Record)RecordType— nullableRecordTypes(default:null— все)
DatabaseSettings
SettingsPreset для конфигурации Addressables-драйвера.
databaseLabels—string[], метки Addressable-ассетов- В Editor:
[ValueDropdown("GetLabels")]— dropdown из всех меток, назначенных ассетам в Addressable Asset Settings SettingsModelExtDatabase(partialSettingsModel) —DatabaseLabelsproperty
Использование
1. Создание пресета
[CreateAssetMenu(menuName = "Database/Product")]
public class ProductPreset : RecordPreset<ProductRecord>
{
[SerializeField] private float price;
[SerializeField] private int quantity;
public float Price => price;
public int Quantity => quantity;
}
Для корректной работы CopyFrom() данные должны быть доступны через публичные свойства-геттеры с именами, совпадающими со свойствами Record.
2. Настройка ResourcesDriver
Пресеты размещаются в Assets/Resources/Database/. Драйвер назначается в DriverConfig.
3. Настройка AddressablesDriver
- Пометить пресеты метками в окне Addressables
- В ассете
DatabaseSettingsуказать метки вdatabaseLabels - Назначить драйвер в
DriverConfig
4. Атрибут DbRecord
[SerializeField, DbRecord(typeof(Sound))]
private string audioSample;
5. Editor API
#if UNITY_EDITOR
var driver = Database.GetDriver() as IDriverEditor;
driver?.ReloadDatabase();
var preset = driver?.GetPresetForRecord(guid);
#endif
Редакторские инструменты
RecordPreset.ResetGuid()— кнопка генерации нового GUIDRecordPreset.OnNameChanged()— автоматическое переименование файла ассета. При попытке задать имя, начинающееся не с буквы (цифра, символ, пробел), подставляетсяDefaultName— защита от невалидных имён ассетов, которые ломают C#-кодогенерацию.RecordPresetdebug-поле_data— показывает текущее состояние записи в инспекторе в Play-режиме. Кешируется на 1 секунду (DebugDataRefreshInterval) — для глаза «живо», для процессора — раз в секунду, без аллокацийCopyFromна каждый OnGUI. Скрыто для MultiInstance-пресетов и в Edit-режиме.DbRecordAttribute— picker с фильтрацией по типу и режиму. Кеш TTL 1 сек:ReloadDatabase()и пересборка списка имён/GUID'ов выполняются раз в секунду на уникальный(RecordType, RecordClass), а не на каждый OnGUI. Это критично для инспектора с несколькими[DbRecord]-полями над большой базой (например, Sound-каталог с сотнями записей) — без кеша FPS падает на порядок. При смене ассетов в проекте лаг видимости в выпадашке — до 1 сек.IDriverEditor.ReloadDatabase()— обновление кеша без перезапускаIDriverEditor.GetPresetForRecord(guid)— получение пресета по GUID- Кодогенерация:
Assets/Create/Vortex Templates/Record,Assets/Create/Vortex Templates/Preset for Record
Preset for Record — генерация свойств
Контракт иммутабельности сгенерированных свойств:
| Тип свойства Record | Поле Preset | Свойство Preset |
|---|---|---|
| Примитив / иммутабельный | T field |
=> field |
List<T> (T иммутабельный) |
T[] field |
=> new List<T>(field) |
List<T> (T ссылочный) |
T[] field |
=> new List<T>(Array.ConvertAll(field, e => e.DeepCopy())) |
Массив иммутабельных (T[]) |
T[] field |
=> (T[])field.Clone() |
| Массив ссылочных / прочие ссылочные | T field |
=> field.DeepCopy() |
Иммутабельность определяется через ObjectExtDeepClone.IsImmutable (примитивы + платформенные типы SimpleTypeMarker).
Граничные случаи
| Ситуация | Поведение |
|---|---|
Пустая папка Resources/Database |
Драйвер инициализируется без данных, OnInit вызывается |
Пустой databaseLabels (Addressables) |
Лог ошибки, загрузка не выполняется |
| Дубликат GUID в Singleton | AddNew — последний перезаписывает |
| Дубликат GUID в MultiInstance | Лог ошибки, GUID не добавляется повторно |
GetNewRecord с несуществующим GUID |
null + лог ошибки |
CheckRecordType с несовпадающим типом |
false |
SetDriver возвращает false |
Экземпляр уничтожается (Dispose()) |
| Переименование пресета в дубликат | Суффикс (N), лог ошибки |
CancellationToken при загрузке |
Загрузка прерывается, OnInit не вызывается |