dmain.pas

来自「FlexGraphics是一套创建矢量图形的VCL组件」· PAS 代码 · 共 475 行

PAS
475
字号
unit dMain;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  IniFiles, ExtCtrls;

const
  // Sections in ini-file
  isMain       = 'Main';
  isPersonnel  = 'Personnel';
  isZones      = 'Zones';
  isPasses     = 'Passes';
  isOptions    = 'Options';

  EmployeeMovementAvgInt = 300; // sec

  MaxEvents    = 50000;
  EventsCut    = 1000;

type
  TPersonType = ( ptIndefinite, ptGuard, ptEmployee );
  TPersonState = ( psPassive, psMovement );

  PPerson = ^TPerson;
  TPerson = record
   Id: integer;
   FirstName: string[31];
   LastName: string[31];
   // Run-time
   PersonType: TPersonType;
   State: TPersonState;
   StateEndTime: TDateTime;
   ZoneId: integer;
   EndZoneId: integer;
   TimeEnter: TDateTime;
   TimeLeave: TDateTime;
  end;

  PZone = ^TZone;
  TZone = record
   Id: integer;
   ParentId: integer;
   Name: string[63];
   // Run-time
   PersonCount: integer;
  end;

  PPass = ^TPass;
  TPass = record
   Id: integer;
   ZoneFromId: integer;
   ZoneToId: integer;
   Name: string[63];
  end;

  PEvent = ^TEvent;
  TEvent = record
   // Run-time
   Time: TDateTime;
   PersonId: integer;
   PassId: integer;
   ZoneId: integer;
   Info: ShortString;
  end;

  TdmMain = class(TDataModule)
   tmMovements: TTimer;
   procedure dmMainCreate(Sender: TObject);
   procedure dmMainDestroy(Sender: TObject);
   procedure tmMovementsTimer(Sender: TObject);
  private
   { Private declarations }
   FIni: TIniFile;
   FUseDB: boolean;
   FPersonnel: TList;
   FZones: TList;
   FOfficeZones: TList; // for movements emulation only
   FPasses: TList;
   FEvents: TList;
   FOnEventAdd: TNotifyEvent;
   procedure LoadFromIni;
   procedure SetupPersons;
   procedure DoPersonMove(Index: integer);
   function  GetZoneById(ZoneId: integer): PZone;
  public
   { Public declarations }
   procedure AddEvent(const EventInfo: string; PassId, ZoneId, PersonId: integer);
   function  FindPerson(PersonId: integer): integer;
   function  FindZone(ZoneId: integer): integer;
   function  FindPass(PassId: integer): integer;
   property  Ini: TIniFile read FIni;
   property  Personnel: TList read FPersonnel;
   property  Zones: TList read FZones;
   property  ZonesById[ZoneId: integer]: PZone read GetZoneById;
   property  OfficeZones: TList read FOfficeZones;
   property  Passes: TList read FPasses;
   property  Events: TList read FEvents;
   property  OnEventAdd: TNotifyEvent read FOnEventAdd write FOnEventAdd;
  end;

var
  dmMain: TdmMain;

function SecToDT(const Sec: integer): TDateTime;

implementation

{$R *.DFM}

uses
  FlexUtils, fMain, fControl, fSplash;

function SecToDT(const Sec: integer): TDateTime;
begin
 Result := Sec / (24*60*60);
end;

// dmMain /////////////////////////////////////////////////////////////////////

procedure TdmMain.dmMainCreate(Sender: TObject);
begin
 dmMain := Self;
 CreateSplash;
 FIni := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
 FPersonnel := TList.Create;
 FZones := TList.Create;
 FOfficeZones := TList.Create;
 FPasses := TList.Create;
 FEvents := TList.Create;
 LoadFromIni;
 SetupPersons;
 Application.CreateForm(TfmMain, fmMain);
 DestroySplash;
end;

procedure TdmMain.dmMainDestroy(Sender: TObject);
var i: integer;
begin
 FIni.Free;
 FOfficeZones.Free;
 for i:=0 to FPersonnel.Count-1 do Dispose(PPerson(FPersonnel[i]));
 FPersonnel.Free;
 for i:=0 to FZones.Count-1 do Dispose(PZone(FZones[i]));
 FZones.Free;
 for i:=0 to FPasses.Count-1 do Dispose(PPass(FPasses[i]));
 FPasses.Free;
 for i:=0 to FEvents.Count-1 do Dispose(PEvent(FEvents[i]));
 FEvents.Free;
end;

function TdmMain.FindPerson(PersonId: integer): integer;
var i: integer;
begin
 Result := -1;
 for i:=0 to FPersonnel.Count-1 do
  if PPerson(FPersonnel[i]).Id = PersonId then begin
   Result := i;
   break;
  end;
end;

function TdmMain.FindZone(ZoneId: integer): integer;
var i: integer;
begin
 Result := -1;
 for i:=0 to FZones.Count-1 do
  if PZone(FZones[i]).Id = ZoneId then begin
   Result := i;
   break;
  end;
end;

function TdmMain.FindPass(PassId: integer): integer;
var i: integer;
begin
 Result := -1;
 for i:=0 to FPasses.Count-1 do
  if PPass(FPasses[i]).Id = PassId then begin
   Result := i;
   break;
  end;
end;

procedure TdmMain.LoadFromIni;
var i, Count: integer;
    s: string;
    Person: PPerson;
    Zone: PZone;
    Pass: PPass;
begin
 FUseDB := FIni.ReadBool(isMain, 'UseDB', False);
 if FUseDB then begin
  // ToDO: load config from database
 end;
 // Read personnel
 Count := FIni.ReadInteger(isPersonnel, 'Count', 0);
 for i:=0 to Count-1 do begin
  s := FIni.ReadString(isPersonnel, 'Item'+IntToStr(i), '');
  if s = '' then continue;
  New(Person);
  try
   FillChar(Person^, SizeOf(Person^), 0);
   Person.Id := StrToInt(ExtractWord(s, 1, ';'));
   Person.LastName := ExtractWord(s, 2, ';');
   Person.FirstName := ExtractWord(s, 3, ';');
   Person.ZoneId := 0; // Initializes as outside zone
   Person.PersonType := ptIndefinite;
   FPersonnel.Add(Person);
  except
   Dispose(Person);
   raise;
  end;
 end;
 // Read zones
 Count := FIni.ReadInteger(isZones, 'Count', 0);
 for i:=0 to Count-1 do begin
  s := FIni.ReadString(isZones, 'Item'+IntToStr(i), '');
  if s = '' then continue;
  New(Zone);
  try
   FillChar(Zone^, SizeOf(Zone^), 0);
   Zone.Id := StrToInt(ExtractWord(s, 1, ';'));
   Zone.ParentId := StrToInt(ExtractWord(s, 2, ';'));
   Zone.Name := ExtractWord(s, 3, ';');
   FZones.Add(Zone);
   if copy(Zone.Name, 1, Length('Office')) = 'Office' then
    FOfficeZones.Add(Zone);
  except
   Dispose(Zone);
   raise;
  end;
 end;
 // Read passes
 Count := FIni.ReadInteger(isPasses, 'Count', 0);
 for i:=0 to Count-1 do begin
  s := FIni.ReadString(isPasses, 'Item'+IntToStr(i), '');
  if s = '' then continue;
  New(Pass);
  try
   FillChar(Pass^, SizeOf(Pass^), 0);
   Pass.Id := StrToInt(ExtractWord(s, 1, ';'));
   Pass.ZoneFromId := StrToInt(ExtractWord(s, 2, ';'));
   Pass.ZoneToId := StrToInt(ExtractWord(s, 3, ';'));
   Pass.Name := ExtractWord(s, 4, ';');
   FPasses.Add(Pass);
  except
   Dispose(Pass);
   raise;
  end;
 end;
end;

procedure TdmMain.AddEvent(const EventInfo: string;
  PassId, ZoneId, PersonId: integer);
var Event: PEvent;
    i: integer;
begin
 if (FEvents.Count >= MaxEvents) {and (EventsCut < MaxEvents)} then begin
  Event := PEvent(FEvents[0]);
  Event.PersonId := 0;
  Event.PassId := 0;
  Event.ZoneId := 0;
  Event.Info := 'System online (old events deleted)';
  for i:=EventsCut+1 to FEvents.Count-1 do begin
   Dispose(PEvent(FEvents[i-EventsCut]));
   FEvents[i-EventsCut] := FEvents[i];
  end;
  FEvents.Count := FEvents.Count - EventsCut;
 end;
 New(Event);
 FillChar(Event^, SizeOf(Event^), 0);
 Event.Time := Now;
 Event.PassId := PassId;
 Event.ZoneId := ZoneId;
 Event.PersonId := PersonId;
 Event.Info := EventInfo;
 FEvents.Add(Event);
 if Assigned(FOnEventAdd) then FOnEventAdd(Self);
end;

procedure TdmMain.SetupPersons;
var i: integer;
    GateZone: PZone;
 function GenerateIndex: integer;
 begin
  // Find random "indefinite" person
  Result := Round(Random*(FPersonnel.Count-1));
  while PPerson(FPersonnel[Result]).PersonType <> ptIndefinite do
   if Result = FPersonnel.Count-1
    then Result := 0
    else inc(Result);
 end;

begin
 RandSeed := 777;
 GateZone := ZonesById[100];
 if Assigned(GateZone) then begin
  // Select 2 guards
  for i:=1 to 2 do with PPerson(FPersonnel[GenerateIndex])^ do begin
   PersonType := ptGuard;
   // Place two guards in gatekeepers office
   ZoneId := 100;
   State := psPassive;
  end;
  GateZone.PersonCount := 2;
 end;
 for i:=0 to FPersonnel.Count-1 do with PPerson(FPersonnel[i])^ do begin
  if PersonType <> ptIndefinite then continue;
  PersonType := ptEmployee;
  State := psPassive;
  StateEndTime := Now + SecToDT(5 + Round(Random*20));
 end;
 Randomize;
end;

procedure TdmMain.DoPersonMove(Index: integer);
var Person: PPerson;
    Zone, SrcZone, DestZone: PZone;
    SrcZones, DestZones: TList;
    Id, Idx: integer;

 procedure MoveTo(Zone: PZone);
 var i, FromZoneId, ToZoneId: integer;
     Pass: PPass;
     Info: string;
 begin
  // Define zone id's
  if Assigned(SrcZone)
   then FromZoneId := SrcZone.Id
   else FromZoneId := 0;
  if Assigned(Zone)
   then ToZoneId := Zone.Id
   else ToZoneId := 0;
  // Find pass
  Pass := Nil;
  for i:=0 to FPasses.Count-1 do with PPass(FPasses[i])^ do
   if (ZoneFromId = FromZoneId) and (ZoneToId = ToZoneId) then begin
    Pass := PPass(FPasses[i]);
    break;
   end;
  if not Assigned(Pass) then begin
   // Pass not found
   Info := Format('%s %s can''t find door from ',
     [Person.FirstName, Person.LastName]);
   if Assigned(SrcZone)
    then Info := Info + SrcZone.Name + ' to '
    else Info := Info + '[outside] to ';
   if Assigned(SrcZone)
    then Info := Info + Zone.Name
    else Info := Info + '[outside]';
   AddEvent(Info, 0, 0, Person.Id);
  end else begin
   // Pass found. Try to open door.
   if fmMain.DoPass(Pass.Id, Person.Id, true) then begin
    // Door is open. Change person counters for zones
    if Assigned(SrcZone) then SrcZone.PersonCount := SrcZone.PersonCount - 1;
    if Assigned(Zone) then Zone.PersonCount := Zone.PersonCount + 1;
    // Set new person location
    Person.ZoneId := Zone.Id;
    if Assigned(Zone) then begin
     Person.TimeEnter := Now;
     Person.TimeLeave := 0;
    end else begin
     Person.TimeEnter := 0;
     if Assigned(SrcZone) then Person.TimeLeave := Now
    end;
    if Person.ZoneId = Person.EndZoneId then begin
     // Move end
     Person.State := psPassive;
     Person.EndZoneId := 0;
    end else begin
     // Move through zone
     Person.State := psMovement;
     Person.StateEndTime := Now + SecToDT(5 + Round(10*Random));
    end;
    if Assigned(fmControl) then fmControl.RefreshInfo;
   end;
  end;
 end;

begin
 Person := PPerson(FPersonnel[Index]);
 SrcZone := ZonesById[Person.ZoneId];
 DestZone := ZonesById[Person.EndZoneId];
 if Person.ZoneId = Person.EndZoneId then begin
  Person.State := psPassive;
  Person.EndZoneId := 0;
  exit;
 end;
 SrcZones := Nil;
 DestZones := Nil;
 try
  // Build source zone parents list
  SrcZones := TList.Create;
  Zone := SrcZone;
  while Assigned(Zone) do begin
   SrcZones.Add(Zone);
   Id := Zone.ParentId;
   Zone := ZonesById[Id];
  end;
  // Build destination zone parents list
  DestZones := TList.Create;
  Zone := DestZone;
  while Assigned(Zone) do begin
   DestZones.Add(Zone);
   Id := Zone.ParentId;
   Zone := ZonesById[Id];
  end;
  // Search movement path
  Idx := DestZones.IndexOf(SrcZone);
  if Idx > 0 then begin
   MoveTo(PZone(DestZones[Idx - 1]));
  end else
  if SrcZones.Count > 1 then
   MoveTo(PZone(SrcZones[1]))
  else
  if DestZones.Count > 0 then
   MoveTo(PZone(DestZones[DestZones.Count - 1]))
  else
  if Person.EndZoneId = 0 then
   MoveTo(Nil) // move outside
  else begin
   // Way not found
   Person.State := psPassive;
   Person.EndZoneId := 0;
  end;
 finally
  SrcZones.Free;
  DestZones.Free;
 end;
end;

procedure TdmMain.tmMovementsTimer(Sender: TObject);
var i: integer;
    TimeNow: TDateTime;
    Zone: PZone;
begin
 TimeNow := Now;
 // Check personnel movements
 for i:=0 to FPersonnel.Count-1 do with PPerson(FPersonnel[i])^ do begin
  if PersonType = ptIndefinite then continue;
  if (StateEndTime = 0) or (StateEndTime - TimeNow > 0) then continue;
  case PersonType of
   ptEmployee:
     begin
      if State = psPassive then begin
       if FOfficeZones.Count = 0 then continue;
       // Select new office
       Zone := PZone(FOfficeZones[Round(Random*(FOfficeZones.Count-1))]);
       EndZoneId := Zone.Id;
      end;
      DoPersonMove(i);
      if State = psPassive then
       StateEndTime := TimeNow +
         //SecToDT(EmployeeMovementAvgInt + Round(Random*60) - 30);
         SecToDT(Round(Random*EmployeeMovementAvgInt));
     end;
  end;
 end;
end;

function TdmMain.GetZoneById(ZoneId: integer): PZone;
var Index: integer;
begin
 Index := FindZone(ZoneId);
 if Index < 0
  then Result := Nil
  else Result := PZone(FZones[Index]);
end;

end.

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?