↑ Return to Top
In this example, the controller runs in a loop, iterating over all the Knowledge Sources that are active, and executing each. The Blackboard Pattern defines the controller as the decision maker, as to WHICH Knowledge Source to execute and in what order. In this project that is represented simply by a "Priority" enum which orders the Knowledge Sources. For example the WarMachine is top priority and executed first, so any known threats are acted upon, before any decisions are made. The Blackboard Pattern states that each Knowledge Source is responsible for declaring whether it is available for the current state of the problem being analyzed. This could in fact be an autonomous process, as the Knowledge Sources could monitor the Blackboard directly, but in this example there are multiple problems (detected objects) being analyzed, so each Knowledge Source has an IsEnabled property, which is set if there are any valid objects on the Blackboard. The Blackboard Pattern declares that the controller iterates over the Knowledge Sources, each taking their turn at the problem. As this is a multi-problem Blackboard, I decided to stick as closely to the original concept and let each active Knowledge Source iterate over all the problems/objects that meet it's execution criteria. Some descriptions of the Blackboard Pattern include a callback delegate method, which the controller would then act upon to select the next best problem solver (Knowledge Base). There is a good argument that each of these modules could be working autonomously, and reporting back any changes that are needed. This would be a change to the controller concept of the Blackboard Pattern, so I stuck to a flat turn-based system. The only exception being the Radar module, as it is more of an "input" for the Blackboard, rather than an "actor" on the data.
<
ListBox
ItemsSource
=
"{Binding blackboard.CurrentObjects}"
ItemsPanel
"{DynamicResource ItemsPanelTemplate1}"
ItemContainerStyle
"{DynamicResource ItemContainerStyle}"
ItemTemplate
"{DynamicResource ItemTemplate}"
Margin
"20,20,20,10"
Foreground
"#FFDE6C6C"
>
ListBox.Resources
ItemsPanelTemplate
x:Key
"ItemsPanelTemplate1"
Canvas
IsItemsHost
"True"
/>
</
Style
"ItemContainerStyle"
TargetType
"{x:Type ListBoxItem}"
Setter
Property
"Background"
Value
"Transparent"
"HorizontalContentAlignment"
"{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
"VerticalContentAlignment"
"{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
"Canvas.Left"
"{Binding X}"
"Canvas.Top"
"{Binding Y}"
"Template"
Setter.Value
ControlTemplate
Border
x:Name
"Bd"
BorderBrush
"{TemplateBinding BorderBrush}"
BorderThickness
"{TemplateBinding BorderThickness}"
Background
"{TemplateBinding Background}"
Padding
"{TemplateBinding Padding}"
SnapsToDevicePixels
"true"
ContentPresenter
HorizontalAlignment
"{TemplateBinding HorizontalContentAlignment}"
"{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment
"{TemplateBinding VerticalContentAlignment}"
DataTemplate
"ItemTemplate"
Border.Style
"{x:Type Border}"
Style.Triggers
DataTrigger
Binding
"{Binding IsThreat}"
"Red"
"false"
"Green"
Grid
"3"
Image
Height
"48"
Source
"{Binding Image}"
StackPanel
"0,0,0,-30"
"Bottom"
TextBlock
Text
"{Binding Type}"
"{Binding Name}"
"Right"
TextWrapping
"Wrap"
"{Binding DistanceFromDestruction}"
Width
"Auto"
Visibility
"{Binding IsThreat, Converter={StaticResource BooleanToVisibilityConverter}}"
public
Blackboard blackboard {
get
;
set
; }
Controller controller;
MainWindow()
{
InitializeComponent();
DataContext =
this
blackboard =
new
Blackboard();
controller =
Controller(blackboard);
}
private
void
Button_Click(
object
sender, System.Windows.RoutedEventArgs e)
controller.AddSignalProcessor();
interface
IObject
ObjectType Type {
string
Name {
WriteableBitmap Image {
bool
? IsThreat {
ProcessingStage Stage {
int
X {
Y {
IObject Clone();
AllObjects = new List<
new BirdObject(ObjectType.Bird, "", new WriteableBitmap(new System.Windows.Media.Imaging.BitmapImage(new Uri(@"pack://application:,,,/Media/Bird.bmp", UriKind.Absolute))), false, false),
new PlaneObject(ObjectType.Plane, "", new WriteableBitmap(new System.Windows.Media.Imaging.BitmapImage(new Uri(@"pack://application:,,,/Media/Plane.bmp", UriKind.Absolute))), false, false),
new RocketObject(ObjectType.Rocket, "", new WriteableBitmap(new System.Windows.Media.Imaging.BitmapImage(new Uri(@"pack://application:,,,/Media/Rocket.bmp", UriKind.Absolute))), false, false),
};
IncomingObject(IObject obj)
:
base
(ObjectType.Unknown,
null
,
true
)
actualObject = obj;
ProcessedPixels =
[16, 16];
//Paint the image as all red to start with
Image =
WriteableBitmap(48, 48, 72, 72, PixelFormats.Bgr32,
);
[] ary =
[(48 * 48)];
for
(var x = 0; x < 48; x++)
(var y = 0; y < 48; y++)
ary[48 * y + x] = 255 * 256 * 256;
Image.WritePixels(
Int32Rect(0, 0, 48, 48), ary, 4 * 48, 0);
They could be completely separate systems, located in separate places, communicating through secure services. However, all they would need to co-operate in the system is a shared/common interface, as outlined as an essential part of the Blackboard design pattern.
IKnowledgeSource
IsEnabled {
Configure(Blackboard board);
ExecuteAction();
KnowledgeSourceType KSType {
KnowledgeSourcePriority Priority {
Stop();
This is the first Knowledge Source to "act upon the problem", in that it takes the raw radar data from the "Incoming Objects", as presented on the Blackboard, in the CurrentObjects collection, which the radar is reporting to. As explained, this is by far the most processor intensive part of the system. To represent this, I have added a level of granularity into the signal processor, allowing it to only process one small "block" of pixels from a random square of the image. This means that if the random selection is unlucky, it can take many attempts, before the image processor returns enough to identify the object. This is demonstrated in the ProcessAnotherBit method.
override
IsEnabled
(var ix = 0; ix < blackboard.CurrentObjects.Count(); ix++)
if
(blackboard.CurrentObjects[ix].Stage < ProcessingStage.Analysed)
return
false
ExecuteAction()
ProcessAnotherBit(blackboard.CurrentObjects[ix]);
ProcessAnotherBit(IObject obj)
GRANULARITY = 16;
blockWidth = obj.Image.PixelWidth / GRANULARITY;
ProcessAnotherBit acts upon the image, which in WPF terms is a WriteableBitmap. The method copies the known/shown image (to be worked upon) into an array of bytes, representing the image pixels.
stride = obj.Image.PixelWidth * obj.Image.Format.BitsPerPixel / 8;
byteSize = stride * obj.Image.PixelHeight * obj.Image.Format.BitsPerPixel / 8;
var ary =
byte
[byteSize];
obj.Image.CopyPixels(ary, stride, 0);
And a similar array for the hidden (to be analyzed) image:
var unk = obj
as
IncomingObject;
unk.GetActualObject().Image.CopyPixels(aryOrig, stride, 0);
It then chooses a random square from "aryOrig" and copies the pixels into the known/shown byte array "ary":
(var iy = 0; iy < blockWidth; iy++)
(var ix = 0; ix < blockWidth; ix++)
(var b = 0; b < 4; b++)
ary[curix] = aryOrig[curix];
curix++;
curix = curix + stride - (blockWidth * 4);
And finally returns the updated array to the actual WriteableBitmap:
obj.Image.WritePixels(
Int32Rect(0, 0, obj.Image.PixelWidth, obj.Image.PixelHeight), ary, stride, 0);
This knowledge Source is responsible for trying to figure out what the pixilated image is. This is done on every "pass" of the data, as the image takes form. Every time a block of pixels is added to the known image, it is compared against known images to see if it now only matches ONE of the images. Although not shown in this simple example, it could also trigger if all remaining images are hostile, allowing the War Machine to get involved before the image is even fully analyzed.
This is an ugly piece of byte crunching code not worth documenting, except for the actual ARGB comparison:
var argb1 = (ary[curix + 1] * 256 * 256) + (ary[curix + 2] * 256) + ary[curix + 3];
var argb2 = (aryKnown[curix + 1] * 256 * 256) + (aryKnown[curix + 2] * 256) + aryKnown[curix + 3];
(argb1 != 255 * 256 * 256 && argb1 != argb2)
nomatch =
break
curix += 4;
As you can see, it is iterating through the array of bytes, pixel by pixel (4 bytes at a time). This code specifically checks the RGB values for a match. If the detection code finds only one match remains, then the data is retrieved for the hidden object, representing the "found you" moment, when you pull the relevant data from lookup databases, and potentially representing many other Knowledge Sources which may have been watching the Blackboard for such updates.
(matches.Count() == 1)
obj.Type = matches[0].Type;
obj.Name = matches[0].Name;
obj.IsThreat = matches[0].IsThreat;
obj.Image =
WriteableBitmap(matches[0].Image);
//Create new image instance
(obj.Type != ObjectType.Plane)
obj.Stage = ProcessingStage.Identified;
else
obj.Stage = ProcessingStage.Analysed;
This Knowledge Source is just an example of another analyzer layer that could process the data further. We have BirdObjects (safe) and RocketObjects (hostile) but we also have PlaneObjects, that could be either, depending on further analysis. In this module's Execute method, it emulates the process of further analysis, whether that be initiating a hand-shake protocol to onboard computer identification systems, a manual contact attempt from air traffic controllers, and checking the resulting data against published flight paths. This is a good example where a callback method would be used, as further analysis could take "human time", which is of course, the slowest of all. In this simple example, this is represented by further extraction of data from the hidden actual IObject, within the IncomingObject:
for (var ix = 0; ix < blackboard.CurrentObjects.Count(); ix++)
var obj = blackboard.CurrentObjects[ix];
if (obj.Stage == ProcessingStage.Analysed && obj.Type == ObjectType.Plane)
var unk = obj as IncomingObject;
var actual = unk.GetActualObject();
obj.Name = actual.Name;
obj.IsThreat = actual.IsThreat;
This final Knowledge Source represents one possible "solution" to the problem :) To add extra "peril" to this example, it takes three seconds (passes) for the military to "respond and react" to an object that is identified and labeled as hostile. With each pass of the War Machine, each 'military response' gets closer to it's target. This represents the delay it takes for Missile Defense Systems to respond, SAMs or "rapid response" type fighter jets to scramble and intercept the target.
The "final blow" is represented by a little piece of WriteableBitmap manipulation, to draw a cross over the image:
var obj = blackboard.CurrentObjects[ix]
(obj.IsThreat !=
&& obj.IsThreat.Value && (obj.Stage != ProcessingStage.Actioned))
(obj.MoveHitsTarget())
DestroyTarget(obj);
DestroyTarget(IncomingObject obj)
DrawCross(stride, ary);
obj.Stage = ProcessingStage.Actioned;
static
DrawCross(
stride,
[] ary)
(var y = 1; y < 47; y++)
var line1Pos = (y * stride) + (y * 4);
var line2Pos = (y * stride) + (stride - 4) - (y * 4);
(var a = -1; a < 2; a++)
ary[line1Pos + 4 + (a * 4)] = ary[line2Pos + 4 + (a * 4)] = 255;
ary[line1Pos + 5 + (a * 4)] = ary[line2Pos + 5 + (a * 4)] = 0;
ary[line1Pos + 6 + (a * 4)] = ary[line2Pos + 6 + (a * 4)] = 0;
ary[line1Pos + 7 + (a * 4)] = ary[line2Pos + 7 + (a * 4)] = 0;
Considering how long the Signal processor can take to process the radar data, added to the extra delay in hitting the targets, means some objects may not get processed in time. This is of course unacceptable in such a system, so it would need to expand, grow with demand. As our controller is simply iterating over a list of known Knowledge Sources, it is a simple matter of adding to that collection. This is demonstrated with the "Add another signal processor" button. each click of this adds extra processing power to each loop of the controller, and after just a few clicks, you will see the images getting identified within just a few iterations. This represents one of the main selling points of this design pattern. Extra Knowledge Sources will also bring their own priority attributes, allowing it to slip into the collection at whatever priority is required. This is also why the Controller is needed, to centralize the decision process and mediate between multiple Knowledge Sources.
This project is available for download and further study in the TechNet Gallery: http://gallery.technet.microsoft.com/Blackboard-Design-Pattern-13a35a7e