Изучаем Cerebrum 1.0
Занятие «Streams-01»
В этом занятии мы научимся конфигурировать пользовательский интерфейс Cerebrum и рассмотрим работу с файловыми потоками находящимися в ООБД.
Проект с рассматриваемым примером Cerebrum.Samples.Streams-01 находится в подкаталоге CLRI
Для запуска примера достаточно запустить
файл Binary\Application\Debug\Cerebrum.DesktopClient.exe
В меню File будут доступны следующие команды:
Data Manager - позволяет посмотреть структуру БД конфигурации системы
New Stream ... - создает новый файл БД из примера Streams-01
Open Stream ... - открывает ранее созданный файл БД из примера Streams-01
Упражнение 1 Настройка базы данных конфигурации СООБЗ Cerebrum
Конфигурация системы Cerebrum находится в master DB которая расположена в файле Binary\Application\Debug\Cerebrum.Database.Master.bin . В этой базе находится информация о зарегистрированных классах и элементах управления, необходимых для запуска приложений в среде СООБЗ. В процессе этого упражнения мы удалим оригинальную DB конфигурации, восстановим ее из экспорта пустой DB а затем в ручную восстановим информацию, необходимую для запуска приложения Cerebrum.Samples.Streams-01. Для этого необходимо зарегистрировать класс Cerebrum.Samples.Streams01.SampleAppUiService в БД конфигурации.
Так же необходимо провести регистрацию элементов управления, запускающих данный пример на выполнение.
1.1 Удалить из каталога Binary\Application\Debug\ файл Cerebrum.Database.Master.bin
Qualified Type Name = “Cerebrum.Samples.Streams01.SampleAppUiService, Cerebrum.Samples.Streams-01”
Kernel Object = “Warden”
Qualified Type Name определяет имя типа объекта который будет создан в ООБД. Реализация этого объекта находится в проекте Cerebrum.Samples.Streams-01 в файле SampleAppUiService.cs (CLRI\Cerebrum.Samples.Streams-01\SampleAppUiService.cs)
ParentId = -7
Display Name = “New Stream ...”
Merge Order = “1”
ParentId = -7
Display Name = “Open Stream ...”
Merge Order = “2”
MessageId = “65561”
Ordinal = “1”
NormalLeaveImagePathName = “Images\elex-new.ico”
MessageId = “65562”
Ordinal = “2”
NormalLeaveImagePathName = “Images\elex-open.ico”
После выполнения данных шагов в каталоге Binary\Application\Debug\Export\ появится новый файл конфигурации. При необходимости повторной конфигурации системы можно заменить старый файл, находящийся в подкаталоге Binary\Application\Debug\Import\ на этот новый. После удаления хранилища ООБД (п.2.1) и загрузки новой конфигурации (п.4) шаги с п.6 по п. 57 окажутся лишними.
Упражнение 2 Запуск примера использования потоков СООБЗ Cerebrum
Возможности и архитектура системы
Основная цель данной разработки - создать виртуальную машину, поддерживающую нейронные сети со свободной топологией и числом нейронов до 2 млрд в одном хранилище. Данная возможность обеспечивается путем реализации системы управления сетевой объектно-ориентированной базы знаний (СООБЗ). Поэтому только часть объектов находится в данный момент в оперативной памяти. Большая часть объектов заморожена в файловом хранилище.
Учитывая, что в случае применения реляционной модели данных моделирование нейронной сети потребует выполнения запросов с большим количеством соединений и дальнейшего отображения табличных структур на сеть объектов, применение систем управления реляционными базами данных для решения данной задачи не оправдано. В некоторых случаях, применение ООБД может быть более целесообразно, по сравнению с РБД. Это связанно с тем, что модель реального мира представлена в виде отношений между понятиями. В случае реализации информационных систем ориентированных на сложную бизнес ситуацию возникают значительные трудности при сопровождении изменений реляционной модели описывающей отношения между бизнес объектами.
Система управления сетевой объектно-ориентированной базой знаний Cerebrum обладает следующими возможностями:
- Сохранять текущее состояние графа объектов или нейронной сети в СООБЗ между сеансами работы с пользователем. В том числе сохраняется текущая топология сети объектов. При повторном запуске приложения не понадобится создавать сеть объектов заново.
- При большем количестве экземпляров объектов ограничить объем памяти, используемый графом объектов или нейронной сетью. Наиболее часто используемые объекты остаются в оперативной памяти, остальные вытесняются в файловое хранилище и загружаются в оперативную память по мере необходимости. При загрузке экземпляра в оперативную память он вытесняет другие, редко используемые объекты.
Ограничение объема памяти позволяет избавиться от использования файла подкачки операционной системы, что значительно повышает производительность моделирования сетей с большим количеством экземпляров объектов (при суммарном размере всех экземпляров большем, чем размер текущей свободной памяти в системе)
В случае, если объем сети объектов меньше чем размер текущей свободной памяти в системе, вся сеть находится в оперативной памяти и потерь производительности, связанных с сериализацией-десериализацией .не возникает.
- Применение СООБЗ не накладывает никаких ограничений на используемую бизнес логику или математическую модель нейрона, которую можно реализовать как методы объектов, находящихся в СООБЗ. Основное требование - организовать связи между объектами в сети не с помощью указателей, а с помощью ID объектов. При этом будет необходимо получать указатель на объект используя API СООБЗ.
Общая архитектура текущей версии СООБЗ Cerebrum представляет собой однопользовательскую файловую (desktop) базу. Поддерживаются однопользовательские одноуровневые транзакции. Во время транзакции файловое хранилище отключается, и все изменения объектов накапливаются в оперативной памяти. В будущем предполагается реализации поддержки иерархических транзакций, блокировок объектов, управление версиями (versioning) объектов, конкурентного доступа, многопользовательского режима и клиент-серверной архитектуры.
Архитектура СООБЗ
Объекты могут содержать методы. Имеется возможность реализовать методы managed объектов на MC++, VB и C#, и методы unmanaged объектов на C & C++. Для managed объектов рекомендуется применение C# а для unmanaged – C. Методы объектов выполняются на стороне сервера. Однако отсутствие на данный момент клиент-серверной архитектуры приводит к отсутствию практического отличия client-side от server-side.
При реализации методов объектов необходимо учитывать ограничения. Необходимо следить за блокировкой и деблокировкой объектов в памяти. Если этого не делать могут возникнуть неприятности, от потери производительности вплоть до аварии - это зависит от конкретного сценария в котором не правильно была применена блокировка. Для поиска таких ошибок в Cerebrum предоставляется специальная отладочная версия ядра.
Существуют ограничения в поддержке механизма делегатов и следовательно, событий объектов. Делегаты не обрабатываются ядром Cerebrum, следовательно нельзя устанавливать связь между двумя объектами, управляемыми ядром Cerebrum. Это связанно с ограничениями по времени жизни экземпляров. Во время, когда система остановлена, все объекты хранятся в замороженном виде. Если время жизни у приемников или источников сообщений совпадает, или может быть изолированно от времени жизни объектов управляемых системой, то механизмы делегатов - сообщений работает в пределах подсети объектов развернутых в памяти .NET Framework.
Существует возможность проводить отбор хранимых объектов на основе результатов вызовов прикрепленных к ним методов. Эта возможность может быть реализована путем итерации по этим объектам. Учитывая возможность объекта реализовать любой абстрактный интерфейс, возможно использовать виртуальные методы в процессе такого отбора. Другими словами, в Cerebrum можно воспользоваться полиморфизмом, каждый объект имеет право реализовать свою собственную версию метода, на основе выполнения которого произойдет фильтрация объектов.
Интересно отметить, что нейронная сеть обычно характеризуется высоко параллельным режимом работы. Все нейроны работают параллельно и условно независимо друг от друга. Таким образом, в БЗ предусмотрена эмуляция количества threads по количеству нейронов в слое. Предполагается наличие миллионов threads одновременно. Это является важной характеристикой для задачи эмуляции ИНС на последовательной вычислительной машине.
СООБЗ Cerebrum не содержит метаданных классов. Cerebrum использует встроенный в .NET механизм отражения (reflection). Для того чтобы создать новый экземпляр объекта система должна иметь доступ к сборке (assembly) в которой находится реализация класса создаваемого объекта. Это означает, что требуется скопировать сборку на машину, на которой запущено ядро Cerebrum.
Уровень ядра VNPI – хранение и управление временем жизни объектов
Ядро системы включает в себя уровень взаимодействия с операционной системой и уровень хранения и управления объектами. Уровень взаимодействия с операционной системой должен обеспечить адаптацию функций операционной системы к нуждам виртуальной машины, реализующей нейронную сеть. Уровень хранения объектов должен обеспечить хранение и быстрый доступ к объектам и их связям, а также управление временем жизни объектов. Совокупность API всех уровней виртуальной машины, для определенности, названа Virtual Neural Programming Interface, далее VNPI .
Библиотека управляемых компонентов СООБЗ
К эффективности языка реализации ядра виртуальной машины, эмулирующей нейронную сеть со многими миллионами эмулируемых нейронов, предъявляются повышенные требования[1]. В результате, единственным кандидатом в качестве языка программирования для такой системы становится generic C (но уже не C++). Ядро необходимо разработать учитывая современные компонентные технологии. В противном случае будет невозможно реализовать полиморфное взаимодействие различных модулей ядра и динамическое изменение связей между ними. Существующие промышленные реализации компонентной идеологии, например Microsoft COM, слишком громоздки для применения в виртуальной машине, эмулирующей нейронную сеть требуемых размеров. Неэффективность COM в первую очередь связанна с необходимостью выполнять множество команд сравнения GUID при каждом вызове QueryInterface. В качестве компонентной архитектуры для реализации виртуальной машины семантической нейронной сети предлагается применить оригинальную разработку. Основное отличие предлагаемой архитектуры – отсутствие необходимости вызова QueryInterface и поиска нужного интерфейса по значению GUID. В условиях ядра виртуальной машины можно определить время жизни любого внутреннего объекта на этапе разработки. Поэтому в предлагаемой архитектуре отсутствует подсчет ссылок на объекты. Благодаря отсутствию подсчета ссылок увеличивается производительность ядра. Указатель на объект представляет собой указатель на структуру с данными объекта, а не на интерфейс. Этим обеспечивается отсутствие необходимости приведения указателя на объект к различным типам при множественном наследовании интерфейсов. Для описателя классов применяется специализированный диалект языка XML – IDL. На его основе генерируется прототипы объектов и прототипы методов. Среда разработки виртуальной машины должна обеспечивать максимальный комфорт программисту. Поэтому разбор IDL реализуется на языке C# для платформы Microsoft .NET. В процессе синтаксического разбора анализируется содержимое DOM-XML и на его основе формируется объектная модель IDL путем создания и инициализации объектов. После построения объектной модели IDL на ее основе формируются исходные тексты прототипов функций на языке C [2]. Ядро VNPI предоставляет компоненты базового уровня для реализации объектно-сетевой базы. Оно включает в себя компоненты взаимодействия с операционной системой, управления памятью с подсистемой сборки мусора, управления структурированным хранилищем объектов, подсистему кэширования объектами и управления их временем жизни в оперативной памяти.
Как известно, в ООБД применяют "мягкие" указатели. Идентификатор объекта можно рассматривать как указатель на объект в памяти системы. Однако от запуска к запуску указатель в памяти на объект, очевидно, не будет постоянен. Для того, чтобы избежать необходимости сканирования объектов в БД систему реализуют так, чтобы идентификатор объекта не зависел от времени. Это можно сделать по-разному, через индексы, двойную разадресацию ... Этот момент весьма сильно критикуется сторонниками реляционных баз данных (РБД) т.к. сильно сближает РБД и ООБД: и там и там доступ к объекту осуществляется через дополнительную операцию - обычно и там и там - поиск по индексу. В Cerebrum применяются мягкие идентификаторы объектов. В одном handle space одному идентификатору всегда соответствует один и тот же объект. На протяжении жизни объекта этот идентификатор не изменяется. Тип данных для идентификатора объекта - Cerebrum.Runtime.NativeHandle. Размер идентификатора объекта - 32 бита. Поиск объекта в индексе по его идентификатору происходит в три этапа. 32 бита разбиваются на 12, 12 и 8 бит. По этим значением строится 3х уровневое дерево индексов. Коллекции объектов организованы на основании того же механизма. Коллекция представляет собой такой же индекс 12-12-8, который производит отображение одного идентификатора на другой идентификатор. Для доступа к объекту таблицы первым этапом производится восстановление идентификатора объекта по его индексу в коллекции (это 3 операции поиска в дереве). Вторым этапом производится восстановление указателя на объект по его идентификатору (такие же 3 операции по аналогичному, но другому дереву). В итоге, для доступа к строке таблицы надо выполнить 6 операций поиска в индексе, не зависимо от количества объектов в базе.
Модель хранения данных в Cerebrum представляет собой граф с раскрашенными ребрами, узлы графа являются объектами. Объект так же может представлять собой граф с раскрашенными ребрами в узлах которого находятся другие объекты. Каждое ребро имеет только один "цвет". Цвет не является объектом, цвет имеет тот же тип что и идентификатор объекта (Cerebrum.Runtime.NativeHandle). Цвет - это тип отношения. Если не обходимо сопоставить цвет ребра с каким либо объектом, нужно создать некоторый объект и взять его NativeHandle в качестве “цвета” для раскраски ребра. Таким образом, можно раскрасить ребра в "цвета" экземпляров объектов. Или другими словами иметь объекты, соответствующие цветам ребер. Если нужно имитировать раскраску ребра множеством цветов, то надо делать несколько ребер каждое разного "цвета".
Уровень моста .NET-VNPI
Уровень реализации пользовательских объектов должен быть удобным для использования прикладными программистами. На данный момент этому требованию наиболее полно соответствует Microsoft .NET Framework. Для обеспечения интеграции .NET Framework и ядра VNPI необходим мост VNPI - .NET. Мост VNPI - .NET обеспечивает доступ к VNPI из среды .NET Framework. Он так же позволяет реализовывать объекты в среде .NET управляемые подсистемами ядра VNPI.
Учитывая различные цели и требования стоящие перед .NET Framework и VNPI идеология системы сборки мусора кардинально отличается. Так в .NET уничтожение объекта возможно только после того как не останется возможности его использования в текущем процессе[3]. В отличие от этого в ядре VNPI сборка мусора осуществляется, когда исчерпана память допустимая для размещения объектов. В этом случае ненужные объекты вытесняются на устройство долговременного хранения. Уничтожение объектов является частью модели предметной области и поэтому выполняется только в ручную. Мост осуществляет подсчет ссылок на объекты ядра VNPI используемые .NET и препятствует вытеснению объектов, которые могут быть доступны через обращения из контекста .NET
В Cerebrum тип объекта определяется 3 факторами. 1 - тип Kernel Object, 2 - типом .NET объекта, присоединенным к узлу, 3 - комплектом реализованных в данный момент атрибутов. Комплект атрибутов объекта можно менять динамически. Если рассматривать контракт на интерфейс объекта как номенклатуру поддерживаемых атрибутов - то любой объект можно рассматривать как экземпляр любого класса. Это очень удобно.
Основной единицей хранения является узел. Узел представляет собой совокупность нескольких экземпляров объектов. Основными объектами, входящими в узел являются: объект ядра VNPI Kernel Object, объект Connector, объект Containter и объект Component.
Kernel Object представляет собой Native VNPI Object реализованный на языке C. У прикладного разработчика доступ к этому объекту полностью отсутствует. Kernel Object реализован согласно требований, описанных в[2].
Connector - managed object представляет собой корень узла. Он связывает в единое целое объекты входящие в узел, благодаря тому, что имеет ссылки на пользовательский объект, на Kernel Object, на родительский Connetctor. С помощью Connector можно получить ссылку на объект-оболочку (Container), который обеспечивает прикладному программисту доступ к функциональности, предоставляемой Kernel Object. Экземпляр этого объекта создается и предоставляется системой моста. Большинство служебных функций системы возвращают экземпляры оболочек для объекта Connector.
Container - Managed Object который представляет собой оболочку для Kernel Object. Может отличаться в разных узлах в зависимости от типа Kernel Object. Предоставляет интерфейсы по управлению внутренним состоянием Kernel Object.
Component - представляет экземпляр пользовательского объекта. В минимальном сценарии ограничения не накладываются. Может быть экземпляром любого класса среды .NET Framework унаследованного от System.Object. Однако во многих сценариях будет рационально реализовать интерфейсы IComponent и при необходимости ISerializable. Рекомендуется наследовать пользовательские объекты от Cerebrum.Integrator.GenericComponent
В системе реализована собственная версия Garbage Collector с альтернативным алгоритмом выполнения. Когда память исчерпывается, самые старые объекты вытесняются на диск, освобождая место более нужным в данный момент. Таким образом, объекты, которые находятся в использовании, нужно защищать от принудительной выгрузки из оперативной памяти. Если не выполнить блокировку, то объект может быть разрушен в памяти еще до момента окончания его модификации. Для реализации подсчета ссылок на объекты Kernel Object в место экземпляров объектов Connector и Containter пользователю возвращаются их объекты-оболочки. Каждый вызов функции, возвращающий эти объекты, приводит к созданию нового объекта оболочки. В конструкторе объекта-оболочки производится увеличение значения счетчика ссылок, а в деструкторе – уменьшение его значения. Важной особенностью данной системы является необходимость вызова метода Dispose у всех объектов-оболочек для предотвращения чрезмерного расхода памяти. В случае, если программист забудет вызвать Dispose, этот вызов будет осуществлен автоматически при сборке мусора в среде .NET Framework.
Типичный прецедент вызова метода у пользовательского объекта выглядит следующим образом:
NativeHandle someObjectHandle = ...;
IConnector someConnector = ...;
using(IContainer container =
someConnector.GetContainer() as IContainer)
{
using(IConnector connector
Система сама следит за временем жизни пользовательских объектов в памяти. Инициировать сохранение объектов в ручную не нужно. Наименее часто используемые объекты автоматически вытесняются на систему долговременного хранения, освобождая оперативную память. Поэтому в любой момент возможно что системе придется произвести вытеснение и разрушение какого то пользовательского объекта. Каждый раз, когда вызывается метод ядра требующий блокировки Kernel Object, создается новый объект-оболочка. В конструкторе оболочки производится увеличение счетчика ссылок. В Dispose - уменьшение счетчика ссылок. Пока хоть одна оболочка находится в памяти, счетчик ссылок не равен 0 и соответствующий этой оболочке объект заблокирован от разрушения. Если программист забудет вызвать Dispose - это произойдет автоматически в момент сбора мусора .NET Garbage collector. Директивы языка C# using нужны, чтобы гарантировать удержание объекта в памяти во время вызова его методов, а затем гарантировать вызов Dispose после окончания работы с объектом. По выходу из using автоматически вызывается Dispose объекта оболочки и уменьшается счетчик ссылок Kernel Object.
Уровень сетевой объектно-ориентированной базы
Уровень сетевой объектно-ориентированной базы отвечает за создание экземпляров пользовательских объектов, а так же за работу с атрибутами объектов. Каждый объект сетевой объектно-ориентированной базы может иметь несколько атрибутов. В качестве значений атрибутов могут выступать другие объекты или простейшие (скалярные) типы данных. Однотипные экземпляры объектов (с совместимыми наборами атрибутов) удобно представлять таблицами и управлять ими с помощью интерфейса пользователя ориентированного на обработку таблиц. Однотипные объекты имеют одинаковый набор атрибутов. Поэтому возможно представить коллекции однотипных объектов в виде таблицы, в которой сроки являются объектами, а колонки - атрибутами этих объектов.
Для создания объектов необходимо иметь информацию об их типах. Средствами сетевой объектно-ориентированной базы можно реализовать табличную структуру, близкую к структуре реляционной базы данных. Поэтому, для унификации информации о типах объектов, используемая этим уровнем, организованна в виде связанных друг с другом таблиц. Информация об объектах зарегистрированных в системе организована в виде нескольких таблиц Types, Attributes, Tables. Это позволяет определять в БД типы объектов .NET в унифицированном виде. Однако, свойства таблиц, строк и колонок в ОО базе отличаются от свойств, принятых в реляционных базах данных. Объектно-ориентированная база позволяет хранить не строки таблиц, как это принято в табличных базах данных, а экземпляры объектов. В пространстве имен Cerebrum.Reflection этим таблицам соответствуют классы TypeDescriptor, AttributeDescriptor, TableDescriptor.
Класс MemberDescriptor представляет собой базовый класс, от которого унаследованы классы TypeDescriptor, AttributeDescriptor, TableDescriptor.
public class MemberDescriptor : Cerebrum.Integrator.GenericComponent
{
public string Name
public string DisplayName
public string Description
}
где
Name - имя экземпляра;
DisplayName - дружественное имя экземпляра предъявляемое пользователю;
Description - описание данного экземпляра.
MemberDescriptor - это базовый класс для классов которые обладают атрибутами имя, дружественное имя и описание. Многие объекты могут иметь такие атрибуты, поэтому удобно наследовать такие объекты от MemberDescriptor.
Колонка таблицы представляет собой сущность, отдельную и независимую от таблицы. Одна и та же колонка может принадлежать разным таблицам. Колонка таблицы описывает некоторый атрибут объекта. Таблица Attributes содержит информацию об атрибутах объектов, находящихся в сетевой объектно-ориентированной базе. Эта таблица содержит экземпляры класса AttributeDescriptor. Дескриптор атрибута описывает имя некоторого атрибута объекта. Он расширяет объект MemberDescriptor добавляя свойства AttributeType, AttributeTypeHandle а также метод GetTypeDescriptor().
public class AttributeDescriptor : MemberDescriptor
{
public Cerebrum.Runtime.NativeHandle AttributeTypeHandle
public System.Type AttributeType
public Cerebrum.Runtime.IConnector GetTypeDescriptor()
}
где
AttributeType возвращает тип данного атрибута;
AttributeTypeHandle возвращает handle типа;
GetTypeDescriptor() возвращает объект-дескриптор типа данного атрибута.
Если в базе находится несколько объектов, которые имеют атрибут имя, то значениями для этого атрибута (колонки) являются имена этих объектов. Атрибуты (колонки) так же являются объектами ООБД. Например, объект-атрибут "Object name" так же имеет имя, так как класс AttributeDescriptor унаследован от класса MemberDescriptor. Очевидно что, значением атрибута "Object Name" для экземпляра класса AttributeDescriptor, описывающего имя объекта является строка "Object name".
Рассмотрим пример. Для свойства Name объекта MemberDescriptor в ООБД создается экземпляр объекта AttributeDescriptor с значениями свойств соответственно:
Name = “Name”;
DisplayName = “Name”;
Description = “Object name”;
AttributeType = typeof(System.String);
GetTypeDescriptor() = экземпляр TypeDescriptor описывающий System.String .
Таким образом экземпляры класса AttributeDescriptor описывают свойства объектов, хранимых в ООБД, в том числе и свойства собственно класса AttributeDescriptor.
Таблица Types содержит описание типов данных (включая пользовательские), которые известны системе и на основе которых система создает экземпляры объектов, помещаемые в сетевую базу. Эта таблица содержит экземпляры классов TypeDescriptor.
public class TypeDescriptor : MemberDescriptor
{
public string QualifiedTypeName
public Cerebrum.Runtime.KernelObjectClass KernelObjectClass
public System.Type ComponentType
}
где
QualifiedTypeName - возвращает имя .NET класса на основе которого создаются пользовательские объекты, например “System.String” ;
KernelObjectClass - возвращает тип объекта ядра VNPI ассоциированного с экземплярами описываемого типа;
ComponentType - возвращает typeof(QualifiedTypeName) тоесть .NET тип описываемый дескриптором.
Свойства, унаследованные от MemberDescriptor были описаны ранее.
Рассмотрим пример. Для свойства QualifiedTypeName создается экземпляр AttributeDescriptor у которого
Name = “QualifiedTypeName”
DisplayName = “Qualified Type Name”
Description = “.NET Qualified Type Name”
AttributeType = typeof(System.String)
GetTypeDescriptor() = экземпляр TypeDescriptor описывающий System.String
Данная структура позволяет описывать типы классов объектов, содержащиеся в ООБД. Описатели типов также являются объектами, содержащимися в ООБД. Поэтому описатели типов описывают сами себя.
Можно заметить, что у объектов разных классов имеются совместимые атрибуты – например у многих объектов есть атрибут “Name” который описывает имя объекта. Объекты являющиеся экземплярами одного и того же класса имеют одинаковый список атрибутов. Логично сгруппировать однотипные объекты в таблицы у которых колонки будут представлять атрибуты, а сроки – собственно экземпляры объектов. Таблица Tables содержит описание всех таблиц, содержащихся в объектной базе данных. Таблицы это тоже объекты. Имя таблицы - значение атрибута "Object name" у объекта таблицы. Каждая таблица имеет две коллекции указателей на объекты. Коллекцию атрибутов (колонок) и коллекцию компонентов (строк). Каждая строка любой таблицы это тоже объект. Если в коллекцию указателей на строки включить указатель на саму таблицу, то таблица станет собственной строкой. Таблица Tables содержит сама себя в качестве строки. И таблица Tables и строка Tables в таблице Tables - это один и тот же экземпляр одного и того же класса TableDescriptor. Этот экземпляр расположен по одному и тому же адресу в памяти. Значениями для колонок таблицы являются значения атрибутов объектов находящихся в строках таблицы. А значением ее колонки "SelectedAttributes" является коллекция колонок этой же таблицы. Хотя желательно держать в одной и той же таблице объекты одного и того же типа - это вовсе не обязательно. Главное чтоб у этих объектов были атрибуты, хотя бы частично совпадающие с колонками данной таблицы. Я рекомендую держать в одной таблице объекты, классы которых унаследованы от некоторого, базового класса, который содержит все колонки данной таблицы в качестве собственных атрибутов. Например колонка "Object name" имеет смыл практически в любой таблице. Объекты могут входить одновременно в разные таблицы как их строки. Дескрипторы атрибутов объектов сами являются объектами и зарегистрированы в таблице атрибутов.
Класс TableDescriptor описывает таблицу из экземпляров некоторых объектов. Свойства, унаследованные от MemberDescriptor соответсвенно описывают имя, дружественное имя и описание данной таблицы.
public class TableDescriptor : MemberDescriptor, System.ComponentModel.IListSource
{
public Cerebrum.Runtime.NativeVector GetSelectedAttributesVector()
public Cerebrum.Runtime.NativeVector GetSelectedComponentsVector()
public System.Type ComponentType
}
где
GetSelectedAttributesVector - метод возвращает коллекцию атрибутов таблицы;
GetSelectedComponentsVector - метод возвращает коллекцию строк таблицы;
ComponentType - свойство возвращает тип объектов содержащихся в строках таблицы.
Методу GetSelectedAttributesVector соответствует экземпляр AttributeDescriptor у которого
Name = “SelectedAttributes”
DisplayName = “SelectedAttributes”
Description = “SelectedAttributes collection”
AttributeType = typeof(System.ComponentModel.IBindingList)
Метод GetSelectedComponentsVector возвращает коллекцию объектов - строк, этому методу соответсвует экземпляр AttributeDescriptor у которого
Name = “SelectedComponents”
DisplayName = “SelectedComponents”
Description = “SelectedComponentscollection”
AttributeType = typeof(System.ComponentModel.IBindingList)
Все экземпляры AttributeDescriptor перечислинны в таблице TableDescriptor.Name = “Attributes”. Все экземпляры TypeDescriptor перечислинны в таблице TableDescriptor.Name = “Types”. Все экземпляры TableDescriptor перечислинны в таблице TableDescriptor.Name = “Tables”. При этом экземпляр класса AttributeDescriptor.Name = “Name” содержится в коллекции строк таблицы Attributes, а так же в коллекциях атрибутов (колонок) таблиц “Attributes”, “Types” и “Tables”. Экземпляр класса TableDescriptor.Name = “Tables” хранится в своей же коллекции строк таблицы (SelectedComponents). Следовательно, применение тавтологии для эмуляции РБД на основе ООБД является оправданным и полезным. Надо заметить, что экземпляры объектов могут одновременно входить в разные таблицы при этом важно, чтоб они поддерживали все атрибуты, являющиеся колонками таблиц в которые они входят в качестве строк.
Таким образом, каждая таблица содержит не некоторые абстрактные строки с данными, а конкретные экземпляры .NET объектов. Колонки таблиц представляют собой атрибуты (custom properties), поддерживаемые этими объектами. Сами типы, атрибуты и таблицы так же являются объектами и представлены строками в соответствующих таблицах. Каждая строка некоторой таблицы представляет собой экземпляр объекта, содержащийся в базе данных. Каждая колонка некоторой таблицы представляет собой атрибут, описанный в таблице Atributes. Экземпляр класса (объект) не зависит от таблицы. Не обязательно создав объект помещать его строкой в какую то таблицу. Будучи созданным, объект будет продолжать существовать либо в оперативной памяти системы либо в качестве потока в файловом хранилище до тех пор, пока не будет удален принудительно. Созданный экземпляр будет сохранять свою идентичность и значения атрибутов через различные циклы запуска и останова системы.
Один и тот же объект имеет право не быть строкой ни в одной таблице или быть строкой в нескольких таблицах одновременно. Так как каждая строка таблицы является экземпляром объекта и имеет свой NativeHandle то эффективнее работать с такими строками методом прямой адресации. Объект не превратится в таблицу, если его добавить строкой в таблицу Tables или в атрибут, если его добавили в таблицу Attributes. Все экземпляры объектов, добавляемые в одну таблицу должны быть унаследованы от одного базового типа, который ожидается пользовательским кодом. В этом случае имеет место стандартный полиморфизм. Чтобы быть добавленным в таблицу Tables объект должен быть либо экземпляром класса TableDescriptor, либо экземпляром класса, унаследованного от класса TableDescriptor. Чтобы быть добавленным в таблицу Attributes объект должен быть либо экземпляром класса AttributeDescriptor, либо экземпляром класса, унаследованного от класса AttributeDescriptor. Эта возможность используется при добавлении объектов различных типов в таблицу UiServices. Таблица UiServices хранит пользовательские объекты, унаследованные от класса Cerebrum.Windows.Forms.Reflection.UiServiceDescriptor
Пользователь имеет возможность создавать новые таблицы путем добавления новых записей в таблицу Tables. Созданные экземпляры таблиц, атрибутов и других объектов так же не обязательно регистрировать в соответствующих таблицах. Однако практика регистрации таких объектов в соответствующих таблицах является рекомендуемой, так как позволяет изменять их атрибуты через унифицированный интерфейс пользователя. Обязательным требованием является регистрация пользовательского типа в таблице Types. В этом случае Integrator.Acivator при необходимости автоматически произведет создание нового экземпляра. В противном случае программист будет обязан реализовать собственную версию интерфейса IActivator.
В ОО БД поддерживаются полноценные C# объекты, у объектов поддерживаются методы, свойства, поля и события. В данной разработке статические методы классов - просто обычные статические методы в .NET а экземплярные методы - обычные методы объектов. Рекомендуется создавать свойства для поддерживаемых классом атрибутов. Механизм делегатов и событий не будет поддерживаться, в случае если связь реализуется между объектами, хранящимися в БД. Это связанно с ограничениями по времени жизни экземпляров. Во время, когда БД остановлена, все объекты хранятся в сериализированном (serialized) виде. Если время жизни у приемников или источников сообщений может быть изолированно от времени жизни объектов БД, то механизмы делегатов - сообщений работают в пределах подсети объектов развернутых в памяти .NET Framework. Система разрабатывается в первую очередь для поддержки нейронных сетей со свободной топологией и числом нейронов до 2 млрд в одном хранилище. Поэтому только часть ОО БД находится в данный момент в RAM. Большая часть объектов заморожена в файловом хранилище и десериализируется (deserializing) по мере необходимости.
Учитывая, что у каждого объекта в такой базе имеется поведение, реализованное в виде методов этого объекта, логично называть такую базу активной. Используя данную разработку, программист полностью свободен создавать собственные таблицы, содержащие объекты-строки собственных типов. Так как классы можно разрабатывать самостоятельно, то строки могут иметь методы, свойства, события и все другие достижения современных объектно-ориентированных методов программирования.
Итак, как мы знаем, все объекты в ООБД имеют свой уникальный идентификатор, по которому их можно быстро найти - это аналог ключа (primary key) с индексом. Само его наличие не дает преимуществ перед реляционными базами данных. Однако возможность нарушать 1 нормальную форму - хранить в одном атрибуте объекта список значений из этих идентификаторов позволяет реализовывать коллекции объектов использующих метод прямой навигации. В реляционной модели уже затруднительно реализовать данную возможность, так как отношения один к многими или составной ключ - это уже не один, а несколько операций поиска по индексам, к тому же прямая навигация в таком случае удобнее, чем соединение (join).
2. Шуклин Д.Е. ТЗ на операционное ядро V1.05.00 // Интернет публикация 2005,
3. Richter Jeffrey Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework //Miller Freeman Inc., MSDN Magazine NOVEMBER 2000 - VOL-UME 15 NUMBER 11 pp. 82 - 86, 89, 90, 92 ISSN #1528-4859
Основой рассматриваемого примера является следующий код, расположенный в файле DatabaseForm.cs
public void PreInit()
{
// Если объект RootVectorHandle уже создан, то AttachConnector вернет его коннектор.
// Иначе создаем только KernelObject
Cerebrum.Runtime.IConnector iv = this.DomainContext.AttachConnector(RootVectorHandle);
if(iv==null)
{
using(Cerebrum.Runtime.NativeSector sector = this.DomainContext.GetSector())
{
sector.Newobj(RootVectorHandle, Cerebrum.Runtime.KernelObjectClass.Vector);
iv = sector.AttachConnector(RootVectorHandle);
}
}
//cleanup
iv.Dispose();
InitList();
}
// Когда текущий итем выбран мы получаем по номеру итема нужную картинку и показываем на экране
private void lstImages_SelectedIndexChanged(object sender, System.EventArgs e)
{
// получили текущий выбранный в списке итем
Cerebrum.Windows.Forms.DropDownStringItem item = lstImages.SelectedItem as Cerebrum.Windows.Forms.DropDownStringItem;
// получили ID итема
Cerebrum.Runtime.NativeHandle objid = (Cerebrum.Runtime.NativeHandle)item.Value;
// получаем сектор - корень БД
using(Cerebrum.Runtime.IConnector connector = this.DomainContext.AttachConnector(objid))
{
// Получили поток по его ID
using(System.IO.Stream stm = connector.GetContainer() as System.IO.Stream)
{
// Загружаем картинку - у нас уже есть поток, именно таким макаром ее правильно грузить в .NET
using(System.Drawing.Image img = System.Drawing.Bitmap.FromStream(stm))
{
picImage.Image = new System.Drawing.Bitmap(img);
}
}
}
}
/// <summary>
/// Тестовое заполнение БД потоками с картинками
/// </summary>
private void btnPopulateImages_Click(object sender, System.EventArgs e)
{
// получаем сектор - корень БД
using(Cerebrum.Runtime.NativeSector sector = this.DomainContext.GetSector())
{
// в пределах БД находим наш корень - список с объектами которые мы создали и будем далее создавать.
// это нужно для того чтоб иметь возможность найти объекты после закрытия приложения.
// Ведь нужно гдето хранить их ID - храним в этом списке
//
// Получаем указатель на коннектор для нашего списка
using(Cerebrum.Runtime.IConnector iv = sector.AttachConnector(RootVectorHandle))
{
// Получаем указатель на интерфейсный объект для списка
using(Cerebrum.Runtime.NativeVector vec = iv.GetContainer() as Cerebrum.Runtime.NativeVector)
{
// цикл из 100 повторений - создаем 100 картинок
for(int i = 1 ; i< 100 ; i++)
{
// создаем новое ID для нового объекта.
Cerebrum.Runtime.NativeHandle objid = sector.NextSequence();
// создаем новый стрим в текущей БД (в пространстве сектора)
sector.Newobj(objid, Cerebrum.Runtime.KernelObjectClass.Stream);
// После того как поток создан мы можем получить его .NET оболочку по ID
using(System.IO.Stream stm = sector.AttachStream(objid))
{
// Создали картинку для теста с номером картинки в ней
using(System.Drawing.Bitmap bmp = CreateImage(100, 100, i.ToString()))
{
// Сохраняем
bmp.Save(stm, System.Drawing.Imaging.ImageFormat.Bmp);
}
stm.Close();
}
// Для того чтобы не потерять ID созданной картинки - сохраняем этот ID в коллекции
vec.SetMap(objid, new Cerebrum.Runtime.NativeHandle(1));
}
}
}
}
InitList();
}
// По имеющимуся в БД списку потоков инициализируем список картинок видимый пользователю
private void InitList()
{
lstImages.Items.Clear();
Cerebrum.Runtime.IConnector iv = this.DomainContext.AttachConnector(RootVectorHandle);
if(iv!=null)
{
using(Cerebrum.Runtime.NativeVector vec = iv.GetContainer() as Cerebrum.Runtime.NativeVector)
{
foreach(System.Collections.DictionaryEntry de in vec)
{
lstImages.Items.Add(new Cerebrum.Windows.Forms.DropDownStringItem(de.Key.ToString(), de.Key));
}
}
}
iv.Dispose();
}
private System.Drawing.Bitmap CreateImage(int width, int height, string text)
{
System.Drawing.Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
System.Drawing.Graphics g = Graphics.FromImage(bmp);
g.Clear(System.Drawing.Color.SeaGreen);
System.Drawing.Pen pen = new Pen(System.Drawing.Color.BlueViolet, 3);
g.DrawEllipse(pen, 3, 3, width - 6, height - 6);
g.DrawString(text, new System.Drawing.Font("Verdana", 12, System.Drawing.FontStyle.Bold), new System.Drawing.SolidBrush(System.Drawing.Color.White), 0, 0);
g.Dispose();
return bmp;
}
/// <summary>
/// Хардкодед значения для списка объектов - потоков.
/// </summary>
private readonly Cerebrum.Runtime.NativeHandle RootVectorHandle = new Cerebrum.Runtime.NativeHandle(60000);