Odmierzanie czasu pomiędzy operacjami, obciążenie procesora

Odpowiedz Nowy wątek
2019-05-07 12:20
0

Kiedyś już zakładałem podobny temat, ale teraz trochę inaczej podszedłem do problemu i osiągnąłem pewne efekty, tylko zastanawia mnie ich wiarygodność. Naskrobałem sobie coś takiego:

type
  TCounter = class(TThread)
    procedure Execute; override;
  end;

  TPainter = class(TThread)
    procedure Execute; override;
  end;

var
  Counter: TCounter;
  Painter: TPainter;
  FPS: Integer;

implementation

procedure UpdateFPS;
begin
  Form1.Caption:= IntToStr(FPS);
end;

procedure TCounter.Execute;
var
  Elapsed: Int64;
  Watch: TStopWatch;
begin
  FreeOnTerminate:= True;
  Watch:= TStopWatch.StartNew;
  while not (Terminated = True) and not (Application.Terminated = True) do
    begin
      Elapsed:= Watch.ElapsedMilliseconds;
      if Elapsed >= 1000 then
        begin
          Synchronize(UpdateFPS);
          Elapsed:= 0;
          Watch.Reset;
          Watch.Start;
          FPS:= 0;
        end;
      Sleep(1);
    end;
end;

procedure TPainter.Execute;
begin
  while not (Terminated = True) and not (Application.Terminated = True) do
    begin
      Synchronize(Draw); //Procedurka służąca do generowania obrazka i rysowania go na ekranie przy pomocy TPaintBox
      Inc(FPS, 1);
      Sleep(1);
    end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Counter.Resume;
  Painter.Resume;
end;

initialization
  Counter:= TCounter.Create(True);
  Painter:= TPainter.Create(True);

finalization
  Counter.Terminate;
  Painter.Terminate;

Od razu zaznaczam: Tak, wiem, nie powinno się używać Suspend i Resume (bo są "przestarzałe"), tylko TMutex i TEvent, ale to tak na szybko, żeby sobie nie komplikować :).

No a właściwie pytanie jest takie: Na ile to co skleciłem jest wiarygodnym miernikiem szybkości? Jestem na etapie testowania różnych algorytmów rasteryzacji i teksturowania, a metoda badawcza "na oko" nie jest wystarczająca, bo muszę mieć twarde dane i wiedzieć, co działa szybciej, a co wolniej.

Druga kwestia: Czym zastąpić Sleep? Znajduje się on w obu wątkach tylko dlatego, że pętle bez niego niemalże mordują procesor. Sam TCounter (którego jedynym zadaniem jest odświeżanie licznika na belce) bez Sleep potrafi wytwarzać nawet 10% obciążenia CPU, a TPainter nawet ponad 20%. Sleep z tego co wiem, nie jest precyzyjne i nie zawsze trwa tyle samo. W przypadku licznika nie stanowi to wielkiego problemu, w końcu nie jest aż tak istotne, czy jego odświeżenie nastąpi raz na 1000 czy tam 1020 milisekund (gołym okiem to nawet niezauważalne), przy rysowaniu "klatek" to już jednak ma znaczenie (i np. bez Sleep(1) uzyskuję 280-290 klatek, z nim jakieś 195-200). Poza tym nawet gdy używam Sleep(1), obciążenie CPU jest dosyć duże, bo wynosi ok. 10%, a CPU mam naprawdę niezły, tj. Intel i7 7700k.

Trzecia sprawa (trochę uboczna): Orientuje się ktoś, czy Direct2D posiada jakieś wbudowane funkcje do mierzenia szybkości renderowania (ilość klatek na sekundę)? Na logikę wydaje się, że tak (bo przecież jest to - nieco uboższy ale jednak - krewniak Direct3D), ale przeszukując neta, na nic takiego nie trafiłem, ani w Delphi, ani w C++.

edytowany 2x, ostatnio: furious programming, 2019-05-07 17:59

Pozostało 580 znaków

2019-05-07 20:46
0
Crow napisał(a):

Cały silnik piszę od zera i własnymi siłami […]

Jeśli eksperymentujesz czy piszesz głównie dla zabawy to nic w tym złego. :]

Skoro tak, to jak działa główna pętla? Używasz delty w celu wyciągania maksymalnej liczby klatek (ewentualnie z opcjonalnym ograniczeniem do 60fps) czy liczysz klatki, jak w grach na stare konsole?


edytowany 1x, ostatnio: furious programming, 2019-05-07 20:46

Pozostało 580 znaków

2019-05-07 21:13
1

Takie małe wtrącenie - mam nadzieję, że jak uda Ci się doprowadzić projekt do końca to się podzielisz z nami wynikiem swojej pracy :)


That game of life is hard to play
I'm gonna lose it anyway
The losing card I'll someday lay
So this is all I have to say
edytowany 1x, ostatnio: cerrato, 2019-05-09 16:17

Pozostało 580 znaków

2019-05-07 21:42
0
furious programming napisał(a):
Crow napisał(a):

Cały silnik piszę od zera i własnymi siłami […]

Jeśli eksperymentujesz czy piszesz głównie dla zabawy to nic w tym złego. :]

Skoro tak, to jak działa główna pętla? Używasz delty w celu wyciągania maksymalnej liczby klatek (ewentualnie z opcjonalnym ograniczeniem do 60fps) czy liczysz klatki, jak w grach na stare konsole?

Mój obecny "game loop" jest bardzo prosty (bo sklecony "na kolanie", żeby móc "podziwiać" na ekranie działanie poszczególnych partii kodu). W zasadzie wyświetla tylko widoczną na screenie skrzyneczkę i jeszcze ją obraca. Kod wygląda tak:

procedure TGameLoop.Execute;
begin
  FreeOnTerminate:= False;
  while not (Terminated) or not (Application.Terminated) do
    begin
      Sleep(25);
      Synchronize(Action);
    end;
end;

procedure TGameLoop.Action;
var
  R: Integer;
  Transformer: TMatrix;
begin

  Transformer:= TMatrix.Create(Identity);
  Transformer.Fill(Multiply(Transformer.Grid, Rotation('Y', Angle * 1.0)));
  Transformer.Fill(Multiply(Transformer.Grid, Translation(0, 0, Distance)));
  Mesh.Vertices.Transformed:= TMatrix.Create(Multiply(Mesh.Vertices.Default.Grid, Transformer.Grid));
  Transformer.Free;

  Mesh.Faces.ReadCentroids;
  Mesh.Faces.ReadNormals;
  Mesh.Faces.TestVisibility(Camera);
  Mesh.Faces.ReadFlatIlluminance(Light);

  Mesh.Vertices.Projected:= TMatrix.Create(Multiply(Mesh.Vertices.Transformed.Grid, ProjectionMatrix.Grid));

  for R:= 0 to Mesh.Vertices.Projected.RowCount - 1 do
    begin
      Mesh.Vertices.Projected[R].Fill(Divide(Mesh.Vertices.Projected[R].Value, [Mesh.Vertices.Projected[R].W, Mesh.Vertices.Projected[R].W, Mesh.Vertices.Projected[R].W, 1]));
      Mesh.Vertices.Projected[R].Fill(Add(Mesh.Vertices.Projected[R].Value, [1, 1, 0, 0]));
      Mesh.Vertices.Projected[R].Fill(Multiply(Mesh.Vertices.Projected[R].Value, [ScreenWidth div 2, ScreenHeight div 2, 1, 1]));
    end;

  Mesh.Draw(Display, RenderMode);
  DrawInfo;

  Form1.SCR.Canvas.Draw(0, 0, Display.Bitmap);
  Mesh.Vertices.Transformed.Free;
  Mesh.Vertices.Projected.Free;
  Display.ResetBuffer;
end;

Sleep(25), żeby nie zamordować mojego procesora :). Osiągam dzięki temu w miarę płynne animacje, przy obciążeniu nieprzekraczającym 4%. Niemniej nie daje mi to oczywiście żadnej kontroli nad wyświetlaniem klatek, ani nawet mierzeniem FPS. Implementację właściwego kodu (z odpowiednim frame bufferem) chcę zrobić już w oparciu o Direct2D, ale postanowiłem, że zanim się za to zabiorę, muszę napisać jakiś prosty miernik prędkości, żeby móc określić, czy w ogóle zmierzam w dobrą stronę. A to dlatego, że jak donosi internet (stackoverflow czy MSDN), początkujący w Direct2D mają zazwyczaj spore problemy z osiągnięciem odpowiedniej wydajności (efekt wychodzi im nierzadko gorszy niż przy renderowaniu na CPU!), bo tam inaczej się zarządza buforami pamięci. Więc znowu, chcę tego uniknąć, ale bez miernika się nie obejdzie. Liczyłem, że być może Direct2D (na wzór mocniejszego brata Directa3D) ma własny miernik tego typu, ale w internecie nic nie wyszperałem, więc chyba nie ;/.

cerrato napisał(a):

Takie małe wtrącenie - dm nadzieję, że jak uda Ci się doprowadzić projekt do końca to się podzielisz z nami wynikiem swojej pracy :)

Spoko, do emerytury na pewno zdążę :). A tak serio, jak tylko uda mi się osiągnąć coś konkretniejszego to oczywiście, jestem skłonny udostępnić cały kod.

edytowany 4x, ostatnio: Crow, 2019-05-07 22:30

Pozostało 580 znaków

2019-05-07 23:21
0
Crow napisał(a):

Mój obecny "game loop" jest bardzo prosty (bo sklecony "na kolanie", żeby móc "podziwiać" na ekranie działanie poszczególnych partii kodu). W zasadzie wyświetla tylko widoczną na screenie skrzyneczkę i jeszcze ją obraca. Kod wygląda tak: […]

Do testów spokojnie wystarczy. To co bym na ten moment zmienił to zawartość tej pętli, tak aby obsługę logiki wykonywać w ramach wątku pobocznego (obstawiam, że TGameLoop jest klasą wątku) i aby jedynie renderowanie finalnej bitmapy w oknie było synchronizowane.

Jeśli chcesz, możesz się wzorować na konstrukcji głównej pętli z mojego platformera. Wygląda tak:

procedure TScene.PlayScene();
begin
  repeat
    FrameBegin(); // pobiera stan zegara przed obsługą klatki

    UpdateInput(); // aktualizuje dane na temat klawiatury
    UpdateState(); // aktualizuje stan gry (stop, reset, power)

    if not Machine.Stopped then
    begin
      UpdatePerformance(); // aktualizuje framerate
      UpdateOptions();     // aktualizuje widoczność liczników
      UpdateLogic();       // aktualizuje logikę gry
      UpdateFrame();       // aktualizuje bufor ramki (generuje obraz klatki)
    end;

    UpdateWindowPlacement(); // aktualizuje rozmiar i pozycję okna
    UpdateWindowInterface(); // aktualizuje płótno okna (odmalowuje je)

    FrameEnd();  // pobiera stan zegara po obsłużeniu klatki i oblicza framerate
    FrameWait(); // oczekuje na kolejną klatkę
  until Finished or Interrupted or Machine.Unavailable;
end;

U Ciebie potrzeba mniej metod, bo zapewne cheat mode nie jest Ci potrzebny, tak samo jak możliwość pauzowania czy zamrażania pracy programu. Ale mimo wszystko możesz taką konstrukcję wykorzystać, dzieląc obsługę klatki na kilka metod:

procedure TGameLoop.Execute();
begin
  FreeOnTerminate := False;

  repeat
    FrameBegin(); // pobiera stan zegara przed obsługą klatki

    UpdateInput(); // aktualizuje dane na temat klawiatury
    UpdateState(); // aktualizuje stan gry (głównie power)

    UpdateLogic(); // aktualizuje logikę gry
    UpdateFrame(); // aktualizuje bufor ramki (generuje obraz klatki)

    UpdateWindowInterface(); // odmalowuje okno (używając Synchronize)

    FrameEnd();  // pobiera stan zegara po obsłużeniu klatki i oblicza framerate
    FrameWait(); // oczekuje na kolejną klatkę (używając np. Sleep)
  until Terminated or Application.Terminated;
end;

Sleep(25), żeby nie zamordować mojego procesora :)

Sleep ogólnie jest prywitywny i ma bardzo niską rozdzielczość (dokładność rzędu zaledwie 16ms), więc szału nima… Jeśli generowanie grafiki klatek jest bardzo wydajne (proste lub akcelerowane sprzętowo) to można z niego skorzystać, w przeciwnym razie trzeba kombinować.

Uniksy w tym temacie biją na głowę Windowsy ze względu na posiadanie funkcji zamrażającej program na określony czas liczony w mikrosekundach. Taka funkcja to skarb.

Osiągam dzięki temu w miarę płynne animacje, przy obciążeniu nieprzekraczającym 4%.

Podziękuj procedurze Sleep. :]

Niemniej nie daje mi to oczywiście żadnej kontroli nad wyświetlaniem klatek, ani nawet mierzeniem FPS.

Daje taką możliwość.

Zwróć uwagę, że główna pętla wykorzystuje mechanizm liczenia klatek, w którym jedna iteracja pętli to obsługa jednej klatki. To powoduje, że framerate jest równy liczbie wykonanych iteracji tej pętli w czasie jednej sekundy. A to powoduje, że wyliczenie framerate'u jest dziecinnie proste.

Zwróć uwagę na przykłady podane wyżej, a konkretnie na metody FrameBegin i FrameEnd. W mojej grze służą one do pobierania stanu sprzętowego zegara (inaczej licznika) i obliczania framerate'u. Zliczanie klatek wykonywanych w danej sekundzie bazuje na testowaniu bieżącej sekundy z systemowego zegara – po prostu wołana jest funkcja Now, z której następnie wyłuskuje się numer sekundy. Jeśli jest on taki sam jak w poprzedniej klatce to inkrementuje się licznik klatek, a jeśli jest inny (nowa sekunda), to zapamiętuje bieżący framerate i resetuje licznik. Proste i wygodne.

Jeśli nie chce Ci się takiego mechanizmu pisać samemu to możesz skorzystać z klasy, z której sam korzystam – źródła całego modułu wrzucam na Pastebin. Jedyne co potrzeba to utworzyć instancję klasy TClock i:

  • w metodzie FrameBegin wywołać TClock.UpdateFrameBegin,
  • w metodzie FrameEnd wywołać TClock.UpdateFrameEnd,
  • w metodzie FrameWait wywołać TClock.WaitForNMI.

To wszystko. Klasa udostępnia kilka podstawowych informacji dotyczących zegara:

  • TClock.Framerate – bieżący framerate (zmienia się co sekundę),
  • TClock.FrameLoad – bieżące obciążenie (aktualizowane co 12 klatek),
  • TClock.FrameIndex – numer klatki.

Jeśli chcesz mieć stałe przerwy pomiędzy klatkami, to usuń zawartość metody WaitForNMI i wstaw do niej swojego Sleepa. Pozostałe opcje możesz pominąć lub usunąć, tak samo jak kod specyficzny dla Uniksów.

Jeśli potrzebujesz bardzo precyzyjnego mechanizmu odmierzania czasu pomiędzy klatkami i utrzymywania stałego framerate'u, to niczego w tej klasie nie zmieniaj (poza ewentualnym wywaleniem kodu dla innych platform). To jednak oznaczać będzie zżeranie całej mocy jednego jądra CPU – coś za coś.

Implementację właściwego kodu (z odpowiednim frame bufferem) chcę zrobić już w oparciu o Direct2D, ale postanowiłem, że zanim się za to zabiorę, muszę napisać jakiś prosty miernik prędkości, żeby móc określić, czy w ogóle zmierzam w dobrą stronę.

Z Direct2D niestety nie pomogę, bo nie znam tej biblioteki i nie używam Delphi, no i w sumie to o profesjonalnym tworzeniu gier nie mam bladego pojęcia. :]


edytowany 10x, ostatnio: furious programming, 2019-05-08 04:14

Pozostało 580 znaków

2019-05-08 18:01
0

Poszedłem za radą i objąłem synchronizacją samo rysowanie, ale coś tu niestety nie działa. Dla testu - żeby wykluczyć wszystkie poboczne elementy, mogące sprawiać problemy - jak zwykle skrobnąłem sobie demko na boku:

type
  TLoop = class(TThread)
    BMP: TBitmap;
    constructor Create; overload;
    procedure Execute; override;
    procedure Draw;
  end;

constructor TLoop.Create;
begin
  BMP:= TBitmap.Create;
  BMP.SetSize(200, 200);
  inherited Create(True);
end;

procedure TLoop.Execute;
begin
  FreeOnTerminate:= True;
  repeat
    BMP.Canvas.Brush.Color:= RGB(Random(256), Random(256), Random(256));
    BMP.Canvas.FillRect(BMP.Canvas.ClipRect);
    Synchronize(Draw);
    Sleep(100);
  until
    Terminated or Application.Terminated;
end;

procedure TLoop.Draw;
begin
  Form1.SCR.Canvas.Draw(0, 0, BMP); //SCR to TPaintBox;
end;

Działanie bardzo proste: Co 100 milisekund losuje kolor i wypełnia nim bitmapę, którą następnie rysuje na Paintboxie. Odpalam, niby działa (zwykle tak przez ok 10-30 sekund), ale nagle jakby zamarza i przestaje zmieniać kolory (choć okno się nie zawiesza, a sama pętla nadal "chodzi"). Paintbox też działa i nadal można po nim mazać, więc wygląda to tak, jakby przestawał działać kod, który ma być synchronizowany. O co w tym chodzi, czemu tak się dzieje i co z tym zrobić?

edytowany 1x, ostatnio: Crow, 2019-05-08 18:04

Pozostało 580 znaków

2019-05-08 18:12
0

Nie wiem co może być tego powodem – może TBitmap nie jest klasą bezpieczną do użycia w wątkach pobocznych i po jakimś czasie, w wyniku ”jakiejś” czynności po prostu zaczyna świrować? W dokumentacji może da się znaleźć informacje na ten temat.

Spróbuj może w ten sposób:

type
  TForm1 = class(TForm)
    procedure SCRPaint(Sender: TObject);
  {..}
  end;

type
  TLoop = class(TThread)
    BMP: TBitmap;
    constructor Create; overload;
    procedure Execute; override;
  end;

procedure TForm1.SCRPaint(Sender: TObject);
begin
  SCR.Canvas.Draw(0, 0, BMP); // tu malowanie bitmapy
end;

constructor TLoop.Create;
begin
  inherited Create(True);

  BMP:= TBitmap.Create;
  BMP.SetSize(200, 200);
end;

procedure TLoop.Execute;
begin
  FreeOnTerminate:= True;

  repeat
    BMP.Canvas.Brush.Color:= RGB(Random(256), Random(256), Random(256));
    BMP.Canvas.FillRect(BMP.Canvas.ClipRect);
    Synchronize(Form1.SCR.Invalidate);
    Sleep(100);
  until Terminated or Application.Terminated;
end;

Jeśli to niczego nie zmieni to widać generowanie bitmap też trzeba synchronizować.

Ewentualnie zastanów się nad tym, aby logikę przetwarzać w ramach głównego wątku – tak jak w moim platformerze. W OnShow formularza wywołaj AsyncCall metody uruchamiającej główną pętlę. Renderowania nie będziesz musiał synchronizować, ale aby kolejka komunikatów była przetwarzana, w każdej klatce wołaj Application.MessageBox.


edytowany 5x, ostatnio: furious programming, 2019-05-08 18:29

Pozostało 580 znaków

2019-05-08 20:18
0
furious programming napisał(a):

Nie wiem co może być tego powodem – może TBitmap nie jest klasą bezpieczną do użycia w wątkach pobocznych i po jakimś czasie, w wyniku ”jakiejś” czynności po prostu zaczyna świrować?

No racja! Przecież generując obrazek używałem TCanvas, które jest częścią VCL, które jak wiadomo, nie lubi się z wątkami pobocznymi. Faktycznie zsynchronizowanie także i tej części kodu, rozwiązało problem, tak samo jak generowanie bitmapy co prawda w dalszym ciągu na pobocznym wątku, ale nie przez TCanvas, tylko ScanLine.

Niestety, w moim silniku to nie pomaga. W wątku pobocznym zostawiłem same obliczenia geometryczne, a wszystko co tykało VCL (czyli w zasadzie jedna funkcja, która rysuje bitmapę na PaintBoxie), pozostawiłem do synchronizacji. Nic z tego, rysowało mi 1 może 2 klatki, a potem czarny ekran i koniec.

Postanowiłem więc sprawdzić, która część kodu "domaga się" synchronizacji. Wczytałem backup i zacząłem wycinać poszczególne linijki kodu z procedury Action (która jest synchronizowana) i przerzucać je do głównej procedury wątku pobocznego, czyli Execute. Wyszła z tego jakaś apokalipsa. Okazało się, że gdy część obliczeń macierzowych wykonuje wątek poboczny, a część synchronizuje do głównego, wszystko się po prostu wykrzacza...

Co jeszcze dziwniejsze, gdy po prostu usuwam synchronize i mam kod:

procedure TGameLoop.Execute;
begin
  FreeOnTerminate:= False;
  while not (Terminated) or not (Application.Terminated) do
    begin
      Sleep(25);
      Action;
    end;
end;

zamiast:

procedure TGameLoop.Execute;
begin
  FreeOnTerminate:= False;
  while not (Terminated) or not (Application.Terminated) do
    begin
      Sleep(25);
      Synchronize(Action);
    end;
end;

Działa... przez kilka sekund, dopóki wątki nie wejdą w konflikt co do dostępu do VCL. To pokazuje, że istnieje jakaś różnica między sytuacją, gdy kod znajduje się bezpośrednio w procedurze Execute, a sytuacją, gdy zamknięty jest w odrębnej procedurze (u mnie Action), która przez Execute jest jedynie wywoływana (nawet bez synchronizacji). Przyznam, że tego nie rozumiem.

===

Przerobiłem w taki sposób, że nadal wywołuję Action wewnątrz Execute, ale tym razem bez synchronize. Synchronizacja odpowiedniej części kodu następuje wewnątrz samego Action i to działa... tyle, że nie widzę żadnego wpływu na wydajność. Co natomiast przyspiesza działanie głównej pętli, to wywołanie całego Action nie przez synchronize, a przez queue (gołym okiem daje to z 15-20 klatek więcej).

edytowany 6x, ostatnio: Crow, 2019-05-08 21:24

Pozostało 580 znaków

2019-05-08 21:35
0
Crow napisał(a):

Faktycznie zsynchronizowanie także i tej części kodu, rozwiązało problem, tak samo jak generowanie bitmapy co prawda w dalszym ciągu na pobocznym wątku, ale nie przez TCanvas, tylko ScanLine.

ScanLine Ci w niczym nie pomoże, bo o ile pozwala na dostęp do bloku pamięci zawierającego dane dotyczące pikseli, to wołając TBitmap.EndUpdate (a ten jest konieczny), klasa aktualizuje dane obrazu oraz modyfikuje zawartość płótna. Tak więc wychodzi na to samo. Jedyna różnica polega na tym, że ScanLine pozwala zmodyfikować dane w sposób efektywny.

Działa... przez kilka sekund, dopóki wątki nie wejdą w konflikt co do dostępu do VCL. To pokazuje, że istnieje jakaś różnica między sytuacją, gdy kod znajduje się bezpośrednio w procedurze Execute, a sytuacją, gdy zamknięty jest w odrębnej procedurze (u mnie Action), która przez Execute jest jedynie wywoływana (nawet bez synchronizacji). Przyznam, że tego nie rozumiem.

W sumie to również nie wiem dlaczego tak się dzieje i co za takie zachowanie odpowiada. Sprawdziłem jak to działa w Lazarusie – no nie ma żadnego problemu z takim programem, nawet jeśli renderowanie klatki oraz odmalowywanie okna wykonywane jest bez synchronizacji, nie ma też wycieków pamięci. Kod projektu i plik wykonywalny w załączniku.

Główna pętla wygląda tak:

procedure TPaintThread.Execute();
begin
  repeat
    GenerateBitmap();
    RedrawBox();

    Sleep(500);
  until Suspended or Terminated or Application.Terminated;
end;

natomiast kod używanych metod tak:

procedure TPaintThread.GenerateBitmap();
begin
  FFrame.Canvas.Brush.Color := RGBToColor(Random(256), Random(256), Random(256));
  FFrame.Canvas.FillRect(0, 0, 200, 200);
end;

procedure TPaintThread.RedrawBox();
begin
  FBox.Invalidate();
end;

FFrame to instancja klasy TBitmap, a FBox to komponent klasy TPaintBox, przekazywany do wątku w jego konstruktorze. No i wszystko śmiga. Jeśli dorzucę synchronizację to też śmiga:

procedure TPaintThread.Execute();
begin
  repeat
    Synchronize(Self, @GenerateBitmap);
    Synchronize(Self, @RedrawBox);

    Sleep(500);
  until Suspended or Terminated or Application.Terminated;
end;

I w czasie działania wątku pobocznego mogę okno przesuwać, przykrywać, minimalizować i przywracać, czyli forsować przemalowywanie okna, a mimo to wszystko nadal będzie działało prawidłowo. Oczywiście synchronizacja związana z operacjami na GUI jest sugerowana, ale dla testów sprawdziłem czy zadziała bez niej.

Tak więc albo wątki w Delphi lub VCL działają inaczej niż w Lazarusie, albo gdzieś jeszcze jest błąd, póki nie namierzony. Trudno mi powiedzieć.


Jeśli nie chcesz się póki co przejmować tym dlaczego wątek sprawia problemy i jego naprawę odłożyć na później, to zrezygnuj z wątku pobocznego i rób wszystko w głównym wątku – też będzie działać, nie zamrażając okna. Jeśli chcesz to Ci pokaże jak napisać taki kod.


edytowany 4x, ostatnio: furious programming, 2019-05-08 21:40

Pozostało 580 znaków

2019-05-09 08:31
1

W nowych Delphi chyba od Tokyo TBitmap podobno jest klasą Thread Safe (nie miałem okazji testować) ale od czegoś są metody Canvas.Lock i Canvas.Unlock więc może trzeba je wywoływać.
EDIT: Nie mam czasu sprawdzać ale to chyba chodziło tylko o FireMonkey
http://docwiki.embarcadero.co[...]tmap,_TCanvas,_and_TContext3D


Nie odpowiadam na PW w sprawie pomocy programistycznej.
Pytania zadawaj na forum, bo:
od tego ono jest ;) | celowo nie zawracasz gitary | przeczyta to więcej osób a więc większe szanse że ktoś pomoże.
edytowany 1x, ostatnio: kAzek, 2019-05-09 09:07

Pozostało 580 znaków

2019-05-09 13:06
0

@kAzek: metod Lock i Unlock używa się w wielowątkowych aplikacjach po to, aby ograniczyć możliwość malowania na płótnie do jednego wątku. Czyli jeden wątek maluje, a inne nie mogą. To w sumie standardowa technika synchronizacji, tak aby wiele wątków nie mogło zmodyfikować zawartości płótna jednocześnie, bo kod może się wykrzaczyć.

Problem jednak polega na tym, że program @Crow zawiera jeden wątek poboczny, który zajmuje się modyfikowaniem zawartości bitmapy. Jedyny problem może wystąpić wtedy, gdy wątek główny używa bitmapy (odczytuje) a poboczny w tym czasie renderuje zawartość (modyfikuje). Tyle że taka kolizja nie powinna zawieszać programu – co najwyżej na wpół zmodyfikowana bitmapa zostanie namalowana w komponencie. No ale ten testowy program jest zbyt prosty, a przygotowywanie bitmapy zbyt krótkotrwałe, aby doświadczyć takiej sytuacji (FillRect działa migiem).


edytowany 2x, ostatnio: furious programming, 2019-05-09 13:08
Ja wiem o tym ale pomyślałem że może chodzić o konflikt dostępu do Canvas kiedy wątek poboczny maluje a główny chce odświeżać. - kAzek 2019-05-09 18:12
Być może tak jest – tego nie wykluczam, bo mam do dyspozycji jedynie teorię (dokumentację). - furious programming 2019-05-09 18:16

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0