从非主线程绘制到主窗体画布

 沛公-若溪 发布于 2022-12-13 16:13

我正在尝试为我的学校项目制作街机游戏.基本思想是在主要的其他线程中进行所有数学和绘图,并仅将主线程用于输入例程.绘图由保存在外部单元中的过程完成,通过创建位图,然后在位图上绘制环境的一部分并最终在主窗体的画布上破坏位图来完成.当我完成绘图程序时,我试图从主线程运行它,并设法使everythink按预期工作(除了整个应用程序窗口被冻结的事实,但由于主线程工作没有停止,sommething像这是预期的).然后我试图将该过程放在其他线程中,并且它停止工作(尽管调试例程报告该过程被重复执行,但它没有绘制任何东西).经过一些添加然后删除的调试例程后,它开始工作没有明显的原因,但不可靠.在大约80%的情况下,它运行平稳,但在其余的情况下它会在十到三十帧后停止,有时甚至不会在最后一帧中卡住一些环境部件.

主窗体单元的重要部分如下所示

procedure TForm1.Button1Click(Sender: TObject);

begin
running:=not running;
if running then AppTheard.Create(false);
end;

Procedure AppTheard.execute;

begin
 form1.Button1.Caption:='running';
 while running do begin view.nextframe; end;
 form1.Button1.Caption:='no longer running';

end;

而另一个单元中的nextframe过程看起来像这样

     Camera = class
 owner:Tform;
 focus:GravityAffected;
 Walls:PBlankLevel;
 Creeps:MonsterList;
 FrameRateCap,lastframe:integer;
 Background:TBitmap;
 plocha:TBitmap;
 RelativePosY,RelativePosX:integer;
 constructor create(owner:Tform; focus:GravityAffected; Walls:PBlankLevel; Creeps:MonsterList; FrameRateCap:integer; background:TBitmap);
 procedure nextframe;
end;    



 procedure camera.nextframe;
 var i,i1,top,topinfield, left,leftinfield: integer ;

  procedure Repair
      //some unimportant math here

  Procedure vykresli(co:vec);
   begin
   if co is gravityaffected then
    plocha.Canvas.Draw(co.PositionX*fieldsize+Gravityaffected(co).PosInFieldX-Left*fieldsize+leftinfield-co.getImgPosX,
     co.PositionY*fieldsize+Gravityaffected(co).PosInFieldY-top*fieldsize+topinfield-co.getImgPosY,
     co.image)
   else
     plocha.Canvas.Draw(co.PositionX*fieldsize-Left*fieldsize+leftinfield-co.getImgPosX,
     co.PositionY*fieldsize-top*fieldsize+topinfield-co.getImgPosY,
     co.image);
   end;

 begin
   // some more unimportant math

  vykresli(focus);                                                 

  For i:= Left+1 to left+2+(plocha.Width div fieldsize) do                                                         //vykreslení zdí
   For i1:= Top+1 to top+2+(plocha.Height div fieldsize) do
    if (i< Walls.LevelSizeX) and (i1< Walls.LevelSizeY) and (i>=0) and (i1>=0) and walls.IsZed(i,i1) then
     begin vykresli(walls^.GiveZed(i,i1)^);end;

  while abs((gettickcount() mod high(word))-lastframe) < (1000 div FrameRateCap) do sleep(1); 
  lastframe:=gettickcount mod high (word);

  owner.Canvas.Draw(-fieldsize,-fieldsize,plocha);     

 end;

有人可以告诉我,我做错了什么?

编辑:我有我要求帮助,但再过几年后,我意识到我真正需要的建议是不是在所有使用线程和尝试像这样来代替.

1 个回答
  • 我看到你的方法有很多不妥之处.

    1)所有VCL交互必须在主线程内完成

    您的线程正在直接访问VCL控件.你不能这样做,因为VCL不是线程安全的.您必须将所有事件同步回主线程,并让主线程执行此操作.

    2)所有自定义UI绘图(到表单)必须在表单的OnPaint事件中完成.

    这解释了为什么它有时而不是其他时间有效.表单会自动绘制,如果您不使用此事件,您的自定义绘图将由VCL绘制.

    3)所有UI绘图必须在主线程内完成

    这将我们带回到第1点和第2点.VCL不是线程安全的.您的辅助线程应该只负责执行计算,但不负责绘制UI.执行一些计算或做一些冗长的工作后,必须将结果同步回主线程,并让主线程执行绘图.

    4)线程应该是完全独立的

    您不应该在此辅助线程中放置任何代码,这些代码知道它将如何显示.在您的情况下,您明确引用表单.你的线程甚至不知道它是否被表单使用.您的线程应该只执行冗长的计算工作,并且绝对考虑用户界面.当您需要指示重绘时,将事件同步回主表单.

    结论

    你需要研究线程安全性.通过这样做,您将能够回答大部分问题.严格地使这个线程只是为了处理繁重的工作,否则会使UI陷入困境.不要太担心缓慢的UI,大多数现代计算机能够在很短的一秒钟内完成复杂的绘图.这不需要在一个单独的线程中.


    编辑

    经过几年的经验,我逐渐意识到上面的#3不一定是真的.实际上,在很多情况下,从线程中执行详细绘制是一种很好的方法,但是主线程只负责将该图像呈现给用户.

    当然,这是它自己的一个完整主题.您需要能够安全地将在一个线程中管理的图像绘制到另一个线程.这也需要使用Synchronize.

    2022-12-13 16:14 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有