BaseMsg.pas
上传用户:yj_qiu
上传日期:2022-08-08
资源大小:23636k
文件大小:17k
源码类别:

游戏引擎

开发平台:

Delphi

  1. (*
  2.  @Abstract(Base messages unit)
  3.  (C) 2003-2007 George "Mirage" Bakhtadze. <a href="http://www.casteng.com">www.casteng.com</a> <br>
  4.  The source code may be used under either MPL 1.1 or LGPL 2.1 license. See included license.txt file <br>
  5.  Unit contains base message classes and message management classes
  6. *)
  7. {$Include GDefines.inc}
  8. unit BaseMsg;
  9. interface
  10. uses BaseTypes;
  11. const
  12.   // Message pool grow step
  13.   MessagesCapacityStep = 16;
  14.   // Initial capacity of messages pool in bytes
  15.   MessagePoolInitialCapacity = 256*256;
  16. type
  17. (*  TMessageDestinationElements = (// Send message to specified recipient. Mutually exclusive with mdChilds.
  18.                                  mdRecipient,
  19.                                  // Send message to childs of specified item. Mutually exclusive with mdBroadcast and mdRecipient.
  20.                                  mdChilds,
  21.                                  // Broadcast message from root item. Mutually exclusive with mdChilds.
  22.                                  mdBroadcast,
  23.                                  // Send message to core handler
  24.                                  mdCore,
  25.                                  // Send message asyncronously
  26.                                  mdAsync);
  27.   TMessageDestination = set of TMessageDestinationElements;*)
  28.   // Type to use as string type in messages. Do not use in messages types which needs finalization (such as dynamic arrays or long strings)
  29.   TMessageString = ShortString;
  30.   { @Abstract(Base class for all message classes)
  31.     Messages are stored in specific pool (see @Link(TMessagePool)) to speed-up allocation and avoid memory leaks. <br>
  32.     As a consequence, messages can be created in such way: <i>SomeObject.HandleMessage(TMessage.Create)</i> without risk of a memory leak. <br>
  33.     <b>Restriction:</b> do not use in messages classes fields of types which needs finalization (such as dynamic arrays or long strings). Use short strings instead. }
  34.   TMessage = class(TObject)
  35.   private
  36.     FFlags: TMessageFlags;
  37.   public
  38.     // This method overridden to store messages in specific pool
  39.     class function NewInstance: TObject; override;
  40.     // If you erroneously deallocate a meesage manually the overridden implementation of this method will signal you
  41.     procedure FreeInstance; override;
  42.     // Call this method if you don't want the message to be discarded
  43.     procedure Invalidate;
  44.     // Message flags
  45.     property Flags: TMessageFlags read FFlags write FFlags;
  46.   end;
  47.   // Message class reference
  48.   CMessage = class of TMessage;
  49.   // Message pool data structure
  50.   TPool = record
  51.     Store: Pointer;
  52.     Size:  Cardinal;
  53.   end;
  54.   PPool = ^TPool;
  55.   { @Abstract(Message pool class)
  56.     The class implements memory management for all instances of @Link(TMessage) and its descendant classes }
  57.   TMessagePool = class                                         
  58.   private
  59.     CurrentPool, BackPool: PPool;
  60.     FCapacity: Cardinal;
  61.     procedure SetCapacity(ACapacity: Integer);
  62.     procedure SwapPools;
  63.     function Allocate(Size: Integer): Pointer;
  64.   public
  65.     constructor Create;
  66.     destructor Destroy; override;
  67.     // Begins message handling. Should be called once per main applicatin cycle
  68.     procedure BeginHandle;
  69.     // Ends message handling and clears messages. Should be called once per main applicatin cycle after <b>BeginHandle</b>
  70.     procedure EndHandle;
  71.   end;
  72.   // Base class for all items. Provides universal message handling interface
  73.   TBaseItem = class
  74.     procedure HandleMessage(const Msg: TMessage); virtual; abstract;
  75.   end;
  76.   // Array of messages
  77.   TMessages = array of TMessage;
  78.   // Message handler delegate
  79.   TMessageHandler = procedure(const Msg: TMessage) of object;
  80.   { @Abstract(Asynchronous messages queue implementation)
  81.     The class provides the possibility to handle asynchronous messages. <br>
  82.     Message handlers can generate other asynchronous messages which will be handled during next handling cycle.
  83.     If you use this class there is no need to call any methods of @Link(TMessagePool). }
  84.   TMessageSubsystem = class
  85.   private
  86.     HandleStarted: Boolean;
  87.     BackMessages, Messages:  TMessages;
  88.     TotalMessages, TotalBackMessages, CurrentMessageIndex: Integer;
  89.     procedure SwapPools;
  90.   public
  91.     { Locks current message queue. Should be called before message handling cycle. <br>
  92.       All asynchronous messages generated during handling will be available during next handling cycle. <br>
  93.       Calls @Link(TMessagePool).BeginHandle so application has no need to call it. }
  94.     procedure BeginHandle;
  95.     // Should be called after handling cycle. Calls @Link(TMessagePool).EndHandle so application has no need to call it
  96.     procedure EndHandle;
  97.     // Add an asynchronous message to the queue
  98.     procedure Add(const Msg: TMessage);
  99.     { Extracts a message from the queue if any, places it to <b>Msg</b> and returns @True if there was a message in queue.
  100.       Otherwise returns @False and @nil in <b>Msg</b>. Should be called only between BeginHandle and EndHandle calls. }
  101.     function ExtractMessage(out Msg: TMessage): Boolean;
  102.   end;
  103.   // Base class for notification messages
  104.   TNotificationMessage = class(TMessage)
  105.   end;
  106.   // This message is sent to an object when it should reset its timer if any
  107.   TSyncTimeMsg = class(BaseMsg.TNotificationMessage)
  108.   end;
  109.   // Pause begin message
  110.   TPauseMsg = class(TMessage)
  111.   end;
  112.   // Pause end message
  113.   TResumeMsg = class(TMessage)
  114.   end;
  115.   // Progress report message
  116.   TProgressMsg = class(TMessage)
  117.     // Progress indicator ranging from 0 to 1
  118.     Progress: Single;
  119.     constructor Create(AProgress: Single);
  120.   end;
  121.   // Base class for system messages
  122.   TSystemMessage = class(TMessage)
  123.   end;
  124.   // Subsystem metaclass
  125.   CSubsystem = class of TSubsystem;
  126.   { @Abstract(Base class for all subsystems)
  127.     Subsystem is a set of routines which implements some specific function and can be connected/disconnected or replaced during runtime. <br>
  128.     Subsystems are usually arranged in one or more classes/units }
  129.   TSubsystem = TBaseItem;
  130.   // Subsystem action type for @Link(TSubsystemMsg) message
  131.   TSubsystemAction = (// subsystem connected
  132.                       saConnect,
  133.                       // subsystem disconnected
  134.                       saDisconnect);
  135.   // This message is sent to a <b>subsystem manager</b> when a subsystem connects or disconnects from the manager
  136.   TSubsystemMsg = class(TSystemMessage)
  137.     Action: TSubsystemAction;
  138.     Subsystem: TSubsystem;
  139.     constructor Create(AAction: TSubsystemAction; ASubsystem: TSubsystem);
  140.   end;
  141.   // When an application receives this message it should shut down as soon as possible
  142.   TForceQuitMsg = class(TSystemMessage)
  143.   end;
  144.   // This message is sent to an <b>application</b> when an option set needs to be applyed (e.g. user clicked "Apply")
  145.   TOptionsApplyMsg = class(TSystemMessage)
  146.     // Option set name to apply
  147.     OptionSet: TMessageString;
  148.     // AOptionSet is the option set name to apply
  149.     constructor Create(const AOptionSet: TMessageString);
  150.   end;
  151.   // This message is sent to an <b>application</b> when an option set needs to be applyed immediately when a user changes it (without clicking the "Apply" button)
  152.   TOptionsPreviewMsg = class(TSystemMessage)
  153.     OptionName, Value: TMessageString;
  154.     constructor Create(const AOptionName, AValue: TMessageString);
  155.   end;
  156.   // This message is sent to an <b>application</b> when it should be notifyed about a particular option set change
  157.   TOptionsApplyNotifyMsg = class(TOptionsPreviewMsg)
  158.   end;
  159.   // Base class for operating system messages
  160.   TOSMessage = class(TMessage)
  161.   end;
  162.   // This message is sent to an <b>application</b> when its window is about to be activated
  163.   TWindowActivateMsg = class(TOSMessage)
  164.   end;
  165.   // This message is sent to an <b>application</b> when its window is about to be deactivated
  166.   TWindowDeactivateMsg = class(TOSMessage)
  167.   end;
  168.   // This message is sent to an <b>application</b> after its window position has changed
  169.   TWindowMoveMsg = class(TOSMessage)
  170.     NewX, NewY: Single;
  171.     // X, Y - new window position in screen coordinates
  172.     constructor Create(X, Y: Single);
  173.   end;
  174.   // This message is sent to an <b>application</b> after its window size has changed
  175.   TWindowResizeMsg = class(TOSMessage)
  176.     OldWidth, OldHeight, NewWidth, NewHeight: Single;
  177.     // <b>OldWidth, OldHeight</b> - old size of the window, <b>NewWidth, NewHeight</b> - new size
  178.     constructor Create(AOldWidth, AOldHeight, ANewWidth, ANewHeight: Single);
  179.   end;
  180.   // This message is sent to an <b>application</b> after its window has been minimized
  181.   TWindowMinimizeMsg = class(TOSMessage)
  182.   end;
  183.   // See WM_CANCELMODE (WinAPI)
  184.   TCancelModeMsg = class(TOSMessage)
  185.   end;
  186.   // This message is sent to an <b>application</b> after a command executon from its window menu
  187.   TWindowMenuCommand = class(TOSMessage)
  188.     Command: Integer;
  189.     constructor Create(ACommand: Integer);
  190.   end;
  191.   // ---
  192.   // If some data may be referenced by pointer and the pointer to the data has changed this message is <b>broadcasted</b> with new pointer
  193.   TDataAdressChangeMsg = class(TNotificationMessage)
  194.     OldData, NewData: Pointer;
  195.     DataReady: Boolean;
  196.     // <b>AOldValue</b> - old pointer, <b>ANewValue</b> - new pointer to the data, <b>ADataReady</b> - determines wheter the data is ready to use
  197.     constructor Create(AOldValue, ANewValue: Pointer; ADataReady: Boolean);
  198.   end;
  199.   // This message is <b>broadcasted</b> when some data which may be used by items has modified
  200.   TDataModifyMsg = class(TNotificationMessage)
  201.     // Pointer, identifying the data. usually it's the address of the data in memory
  202.     Data: Pointer;
  203.     // AData - a pointer, identifying the data. usually it's the address of the data in memory
  204.     constructor Create(AData: Pointer);
  205.   end;
  206.   // Base class for user-input messages
  207.   TInputMessage = class(TMessage)
  208.     constructor Create;
  209.   end;
  210.   // Base class for mouse-related messages
  211.   TMouseMsg = class(TInputMessage)
  212.     // coordinates of the mouse pointer in screen coordinate system
  213.     X, Y: Integer;
  214.     // AX, AY - coordinates of the mouse pointer in screen coordinate system
  215.     constructor Create(AX, AY: Integer);
  216.   end;
  217.   // The message is sent to <b>core handler</b> when the mouse pointer moves
  218.   TMouseMoveMsg = class(TMouseMsg)
  219.   end;
  220.   // Base class for mouse button-related messages
  221.   TMouseButtonMsg = class(TMouseMsg)
  222.     // Button number. usually 1 - left, 2 - right, 4 - middle (see Input.IK_MOUSELEFT etc)
  223.     Button: Integer;
  224.     // <b>AButton</b> - button number
  225.     constructor Create(AX, AY, AButton: Integer);
  226.   end;
  227.   // The message is sent to <b>core handler</b> when a mouse button has been pressed
  228.   TMouseDownMsg = class(TMouseButtonMsg)
  229.   end;
  230.   // The message is sent to <b>core handler</b> when a mouse button has been released
  231.   TMouseUpMsg = class(TMouseButtonMsg)
  232.   end;
  233.   // The message is sent to <b>core handler</b> when a mouse button has been clicked
  234.   TMouseClickMsg = class(TMouseButtonMsg)
  235.   end;
  236.   // The message is sent to <b>core handler</b> when a mouse button has been double clicked
  237.   TMouseDblClickMsg = class(TMouseButtonMsg)
  238.   end;
  239.   // Reference to keyboard message class
  240.   CKeyboardMsg = class of TKeyboardMsg;
  241.   // Base class for keyboard-related messages
  242.   TKeyboardMsg = class(TInputMessage)
  243.     // Scan code of the key
  244.     Key: Integer;
  245.     // <b>AKey</b> - scan code of the key
  246.     constructor Create(AKey: Integer);
  247.   end;
  248.   // The message is sent to <b>core handler</b> when a key has been pressed
  249.   TKeyDownMsg = class(TKeyboardMsg)
  250.   end;
  251.   // The message is sent to <b>core handler</b> when a key has been released
  252.   TKeyUpMsg = class(TKeyboardMsg)
  253.   end;
  254.   // The message is sent to <b>core handler</b> when a key has been clicked
  255.   TKeyClickMsg = class(TKeyboardMsg)
  256.   end;
  257.   // The message is sent to <b>core handler</b> when a key has been double clicked
  258.   TKeyDblClickMsg = class(TKeyboardMsg)
  259.   end;
  260.   // The message is sent to <b>core handler</b> when a character input has been made
  261.   TCharInputMsg = class(TKeyboardMsg)
  262.     // Code of the character
  263.     Character: Char;
  264.     // <b>AChar</b> - code of the character, <b>AKey</b> - scan code
  265.     constructor Create(AChar: Char; AKey: Integer);
  266.   end;
  267. var
  268.   MessagePool: TMessagePool;
  269. implementation
  270. { TMessage }
  271. class function TMessage.NewInstance: TObject;
  272. begin
  273. //  Result := InitInstance(MessagePool.Allocate(InstanceSize));
  274.   Result := TObject(MessagePool.Allocate(InstanceSize));
  275.   PInteger(Result)^ := Integer(Self);
  276. end;
  277. procedure TMessage.FreeInstance;
  278. begin
  279.   Assert(False, 'TMessage and descendants should not be freed manually');
  280. end;
  281. procedure TMessage.Invalidate;
  282. begin
  283.   Include(FFlags, mfInvalid);
  284. end;
  285. { TSubsystemMsg }
  286. constructor TSubsystemMsg.Create(AAction: TSubsystemAction; ASubsystem: TSubsystem);
  287. begin
  288.   Action    := AAction;
  289.   Subsystem := ASubsystem;
  290. end;
  291. { TOptionsApplyMsg }
  292. constructor TOptionsApplyMsg.Create(const AOptionSet: TMessageString);
  293. begin
  294.   OptionSet := AOptionSet;
  295. end;
  296. { TOptionsPreviewMsg }
  297. constructor TOptionsPreviewMsg.Create(const AOptionName, AValue: TMessageString);
  298. begin
  299.   OptionName := AOptionName; Value := AValue;
  300. end;
  301. { TWindowMoveMsg }
  302. constructor TWindowMoveMsg.Create(X, Y: Single);
  303. begin
  304.   NewX := X; NewY := Y;
  305. end;
  306. { TWindowResizeMsg }
  307. constructor TWindowResizeMsg.Create(AOldWidth, AOldHeight, ANewWidth, ANewHeight: Single);
  308. begin
  309.   OldWidth  := AOldWidth;
  310.   OldHeight := AOldHeight;
  311.   NewWidth  := ANewWidth;
  312.   NewHeight := ANewHeight;
  313. end;
  314. { TWindowMenuCommand }
  315. constructor TWindowMenuCommand.Create(ACommand: Integer);
  316. begin
  317.   Command := ACommand;
  318. end;
  319. { TDataAdressChangeMsg }
  320. constructor TDataAdressChangeMsg.Create(AOldValue, ANewValue: Pointer; ADataReady: Boolean);
  321. begin
  322.   OldData   := AOldValue;
  323.   NewData   := ANewValue;
  324.   DataReady := ADataReady;
  325. end;
  326. { TDataModifyMsg }
  327. constructor TDataModifyMsg.Create(AData: Pointer);
  328. begin
  329.   Data := AData;
  330. end;
  331. { TInputMessage }
  332. constructor TInputMessage.Create;
  333. begin
  334.   Flags := [mfCore];
  335. end;
  336. { TMouseMsg }
  337. constructor TMouseMsg.Create(AX, AY: Integer);
  338. begin
  339.   inherited Create;
  340.   X := AX; Y := AY;
  341. end;
  342. { TMouseButtonMsg }
  343. constructor TMouseButtonMsg.Create(AX, AY, AButton: Integer);
  344. begin
  345.   inherited Create(AX, AY);
  346.   Button := AButton;
  347. end;
  348. { TKeyClick }
  349. constructor TKeyboardMsg.Create(AKey: Integer);
  350. begin
  351.   inherited Create;
  352.   Key := AKey;
  353. end;
  354. { TCharInputMsg }
  355. constructor TCharInputMsg.Create(AChar: Char; AKey: Integer);
  356. begin
  357.   inherited Create(AKey);
  358.   Character := AChar;
  359. end;
  360. { TMessageSubsystem }
  361. procedure TMessageSubsystem.SwapPools;
  362. var t: TMessages;
  363. begin
  364.   t            := BackMessages;
  365.   BackMessages := Messages;
  366.   Messages       := t;
  367.   t              := nil;
  368.   TotalBackMessages := TotalMessages;
  369.   TotalMessages := 0;
  370. end;
  371. procedure TMessageSubsystem.BeginHandle;
  372. begin
  373.   HandleStarted := True;
  374.   SwapPools;
  375.   CurrentMessageIndex := 0;
  376.   MessagePool.BeginHandle;
  377. end;
  378. procedure TMessageSubsystem.EndHandle;
  379. begin
  380.   Assert(HandleStarted, 'TMessageSubsystem.EndHandle: Invalid call');
  381.   HandleStarted := False;
  382.   MessagePool.EndHandle;
  383. end;
  384. procedure TMessageSubsystem.Add(const Msg: TMessage);
  385. begin
  386.   if Length(Messages) <= TotalMessages then SetLength(Messages, Length(Messages) + MessagesCapacityStep);
  387.   Messages[TotalMessages] := Msg;
  388.   Inc(TotalMessages);
  389. end;
  390. function TMessageSubsystem.ExtractMessage(out Msg: TMessage): Boolean;
  391. begin                                           // ToDo: Needs testing
  392.   Assert(HandleStarted, 'TMessageSubsystem.ExtractMessage: Should be called only between BeginHandle and EndHandle pair');
  393.   Msg := nil;
  394.   if CurrentMessageIndex < TotalBackMessages then begin
  395.     Msg := BackMessages[CurrentMessageIndex];
  396.     Inc(CurrentMessageIndex);
  397.   end;
  398.   Result := Msg <> nil;
  399. end;
  400. { TMessagePool }
  401. procedure TMessagePool.SetCapacity(ACapacity: Integer);
  402. begin
  403.   FCapacity := ACapacity;
  404.   ReAllocMem(CurrentPool^.Store, ACapacity);
  405.   ReAllocMem(BackPool^.Store, ACapacity);
  406. end;
  407. procedure TMessagePool.SwapPools;
  408. var Temp: Pointer;
  409. begin
  410.   Temp := BackPool;
  411.   BackPool := CurrentPool;
  412.   CurrentPool := Temp;
  413. end;
  414. constructor TMessagePool.Create;
  415. begin
  416.   New(CurrentPool);
  417.   CurrentPool^.Store := nil;
  418.   CurrentPool^.Size  := 0;
  419.   New(BackPool);
  420.   BackPool^.Store := nil;
  421.   BackPool^.Size  := 0;
  422.   SetCapacity(MessagePoolInitialCapacity);
  423. end;
  424. destructor TMessagePool.Destroy;
  425. begin
  426.   SetCapacity(0);
  427.   Dispose(CurrentPool);
  428.   Dispose(BackPool);
  429.   inherited;
  430. end;
  431. function TMessagePool.Allocate(Size: Integer): Pointer;
  432. begin
  433.   Result := Pointer(Cardinal(CurrentPool^.Store) + CurrentPool^.Size);
  434.   Inc(CurrentPool^.Size, Size);
  435.   Assert(CurrentPool^.Size < FCapacity, 'Message pool is full');       // Todo: Handle this situation
  436. end;
  437. procedure TMessagePool.BeginHandle;
  438. begin
  439.   SwapPools;
  440. end;
  441. procedure TMessagePool.EndHandle;
  442. begin
  443.   BackPool^.Size := 0;
  444. end;
  445. { TProgressMsg }
  446. constructor TProgressMsg.Create(AProgress: Single);
  447. begin
  448.   Progress := AProgress;
  449. end;
  450. initialization
  451.   MessagePool := TMessagePool.Create;
  452. finalization
  453.   MessagePool.Free;
  454.   MessagePool := nil;
  455. end.