////////////////////////////////////////////////////////////////////// // // // CpuObservers.pas: CPU Observer Framework // // Observer superclass and observer list management // // // // The contents of this file are subject to the Bottled Light // // Public License Version 1.0 (the "License"); you may not use this // // file except in compliance with the License. You may obtain a // // copy of the License at http://www.bottledlight.com/BLPL/ // // // // Software distributed under the License is distributed on an // // "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or // // implied. See the License for the specific language governing // // rights and limitations under the License. // // // // The Original Code is the Mappy VM User Interface, released // // April 1st, 2003. The Initial Developer of the Original Code is // // Bottled Light, Inc. Portions created by Bottled Light, Inc. are // // Copyright (C) 2001-2003 Bottled Light, Inc. All Rights Reserved. // // // // Author(s): // // Michael Noland (joat), michael@bottledlight.com // // // // Changelog: // // 1.0: First public release (April 1st, 2003) // // // // Notes: // // Mappy VM uses an observer model, where any number of data // // viewers, or 'observers' can be registered, and they'll appear // // in the viewers menu item for the user to activate or close // // at will. When an observer is active, its UpdateObserver // // method is called after each simulation step (either one cycle // // or an entire frame). Observers also call UpdateObservers to // // notify other observers when they've modified the MVM core // // state in any way. // // // // This model has a couple of advantages, most notably less // // usage and extensibility. Memory usage because observer forms // // are created on the fly when needed, rather than kept around in // // memory on the oft chance someone will use it. Extensability // // because making a new observer is a case of creating a new form // // and descending it from TCpuObserver, implementing two methods, // // and adding a RegisterViewer call to the bottom of the unit. // // // // This idea of demand-creation of forms is also used for dialogs // // such as Audio Options. They're also descended from the same // // TCpuObserver, and behave in exactly the same way, except they // // are invoked from a different menu item in the user interface. // // // // A slightly more recent addition is the decentralization of // // loading/saving config data. Now all an observer has to do is // // override two virutal methods and grab/store their config state // // into a TIniFile (registry be damned!) // // // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// unit CpuObservers; /////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// interface //////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// uses SysUtils, Classes, Contnrs, Graphics, Controls, Forms, Math, Menus, IniFiles, console, nexus; ////////////////////////////////////////////////////////////////////// type TCpuObserver = class(TForm) private myItem: TMenuItem; protected procedure Shutdown; procedure observerClose(Sender: TObject; var Action: TCloseAction); public class function OCaption: string; virtual; abstract; procedure UpdateObserver; virtual; abstract; procedure LoadSettings(ini: TIniFile); virtual; procedure SaveSettings(ini: TIniFile); virtual; constructor Create(AOwner: TComponent); override; destructor Destroy; override; end; TCpuObserverClass = class of TCpuObserver; ////////////////////////////////////////////////////////////////////// procedure RegisterViewer(typ: TCpuObserverClass); procedure RegisterDialog(typ: TCpuObserverClass); function CreateObserver(name: string; menuItem: TMenuItem): TCpuObserver; function FindObserver(name: string): TCpuObserver; procedure UpdateObservers; procedure CloseObservers; ////////////////////////////////////////////////////////////////////// var observerList: TObjectList; observers, guiObservers: TStringList; ////////////////////////////////////////////////////////////////////// implementation /////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// procedure RegisterViewer(typ: TCpuObserverClass); begin observers.AddObject(typ.OCaption, TObject(typ)); end; ////////////////////////////////////////////////////////////////////// procedure RegisterDialog(typ: TCpuObserverClass); begin guiObservers.AddObject(typ.OCaption, TObject(typ)); end; ////////////////////////////////////////////////////////////////////// function CreateObserver(name: string; menuItem: TMenuItem): TCpuObserver; var i: integer; begin Result := nil; i := observers.IndexOf(name); if i > -1 then Application.CreateForm(TCpuObserverClass(observers.objects[i]), Result) else begin i := guiObservers.IndexOf(name); Application.CreateForm(TCpuObserverClass(guiObservers.objects[i]), Result); end; if i > - 1 then begin observerList.Add(Result); Result.myItem := menuItem; Result.myItem.Checked := true; Result.Show; end; end; ////////////////////////////////////////////////////////////////////// function FindObserver(name: string): TCpuObserver; var i: integer; begin for i := 0 to observerList.Count - 1 do begin Result := observerList.Items[i] as TCpuObserver; if Result.OCaption = name then Exit; end; Result := nil; end; ////////////////////////////////////////////////////////////////////// procedure UpdateObservers; var i: integer; begin // Update each open observer for i := 0 to observerList.count - 1 do (observerList.Items[i] as TCpuObserver).UpdateObserver; end; ////////////////////////////////////////////////////////////////////// procedure CloseObservers; begin while observerList.Count > 0 do (observerList.Items[0] as TCpuObserver).Close; observerList.Clear; end; ////////////////////////////////////////////////////////////////////// // TCpuObserver ////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// constructor TCpuObserver.Create(AOwner: TComponent); var ini: TIniFile; begin inherited; OnClose := observerClose; // Set some things up Caption := ocaption; // Load the settings ini := TIniFile.Create(appIniFile); LoadSettings(ini); ini.Free; end; ////////////////////////////////////////////////////////////////////// destructor TCpuObserver.Destroy; begin Shutdown; inherited; end; ////////////////////////////////////////////////////////////////////// procedure TCpuObserver.Shutdown; var ini: TIniFile; begin // Save the settings ini := TIniFile.Create(appIniFile); SaveSettings(ini); ini.Free; end; ////////////////////////////////////////////////////////////////////// procedure TCpuObserver.observerClose(Sender: TObject; var Action: TCloseAction); var ref: TCpuObserver; index: integer; begin // Uncheck the observer entry in the view menu ref := Sender as TCpuObserver; ref.myItem.Checked := false; // Close the observer and remove it from the list Action := caFree; index := observerList.IndexOf(Sender); if index > -1 then observerList.Delete(index); end; ////////////////////////////////////////////////////////////////////// procedure TCpuObserver.LoadSettings(ini: TIniFile); var ws: TWindowState; begin ws := TWindowState(Min(2, Max(0, ini.ReadInteger(OCaption, 'WindowState', Ord(wsNormal))))); if ws <> wsMaximized then begin Width := ini.ReadInteger(OCaption, 'Width', Width); Height := ini.ReadInteger(OCaption, 'Height', Height); Left := ini.ReadInteger(OCaption, 'Left', Left); Top := ini.ReadInteger(OCaption, 'Top', Top); end; WindowState := ws; end; ////////////////////////////////////////////////////////////////////// procedure TCpuObserver.SaveSettings(ini: TIniFile); begin ini.WriteInteger(OCaption, 'WindowState', Ord(WindowState)); ini.WriteInteger(OCaption, 'Left', Left); ini.WriteInteger(OCaption, 'Top', Top); ini.WriteInteger(OCaption, 'Width', Width); ini.WriteInteger(OCaption, 'Height', Height); end; ////////////////////////////////////////////////////////////////////// initialization observers := TStringList.Create; guiObservers := TStringList.Create; observerList := TObjectList.Create(false); finalization observerList.Free; guiObservers.Free; observers.Free; end. //////////////////////////////////////////////////////////////////////