Page 1 of 5 123 ... LastLast
Results 1 to 10 of 48

Thread: Cheb's project will be here.

  1. #1

    Chentrah

    (last progress long time ago, next should hopefully be around December 2019 or January 2020)

    The project's website: http://chentrah.chebmaster.com/

    The full saga in Russian http://freepascal.ru/forum/viewtopic.php?f=10&t=10058

    The project history: [pending update and re-checking, will re-post at the same time I upload the public Test #21]
    Last edited by Chebmaster; 12-12-2019 at 09:00 PM. Reason: updating the titular post

  2. #2



    Yet another !surprise! from this newfangled string encoding auto-management.

    I launched my engine compiled in fpc 3.0.4 on the mammoth coprolite I call my file server:
    Code:
    Chentrah version 0.21.3847 for Win32-i386, 
      compiled at 12:21:52 on 2018/01/28 using Free Pascal 3.0.4.
      (developer mode on)
    Operating System: Wine 1.3.28 / Ubuntu 11.10
    User name: cheb
    CPU Phenom II X2 550
      x2 logical cores
      level 2 cache: 512 Kbytes, line size 64 bytes
      TSC invariancy: yes
      TSC frequency: 3.11 GHz)
    Lo and behold it crashed with "Failed to load "GL_ARB_framebuffer_object"".
    Scratching the bone between my ears, I checked the extension string. Nope, the extension was there. So why?

    Unearthed an old function naively assuming that String = AnsiString:
    Code:
    function glext_ExtensionSupported(const extension: String; const searchIn: String): Boolean;
    var
      extensions: PAnsiChar;
      start: PAnsiChar;
      where, terminator: PAnsiChar;
    begin
    
      if (Pos(' ', extension) <> 0) or (extension = '') then
      begin
        Result := FALSE;
        Exit;
      end;
    
      if searchIn = '' then extensions := PAnsiChar(glGetString(GL_EXTENSIONS))
      else extensions := PAnsiChar(searchIn);
      start := extensions;
      while TRUE do
      begin
        where := StrPos(start, PAnsiChar(extension));
        if where = nil then Break;
        terminator := Pointer(PtrUInt(where) + Length(extension));
        if (where = start) or (PAnsiChar(PtrUInt(where) - 1)^ = ' ') then
        begin
          if (terminator^ = ' ') or (terminator^ = #0) then
          begin
            Result := TRUE;
            Exit;
          end;
        end;
        start := terminator;
      end;
      Result := FALSE;
    
    end;
    Corrected it to AnsiString and my engine started up, proving there is life on GeForce 7025.

    But!
    The wrong version was working fine in real Windows.
    I can only assume that that fossilized wine did not have some mechanism the auto-recoding of the FPC RTL relies on.
    That's another thing to watch out for, I suppose.
    Last edited by Chebmaster; 20-11-2019 at 08:58 AM.

  3. #3
    Cheb's Game Engine is that eternally worked on thing that never releases its next build.

    Today, seeing the compilation process complete, I roared like a bear overcoming horrible constipation: I spent a full year refactoring my code. A full year!

    Ahhhhh...

    A debugging hell lies ahead of me but all I could feel is relief. Finally!

    The previous build was released at December 17, 2016 (please don't go looking at it: I am horribly ashamed of that mess by some mistake called my sources).

    The last year I looked at Fidel Castro's example and gave a vow to never shave or trim my beard until my engine goes past the rotating cube stage. I am now forced to resort to dirty life hacks like putting my t-shirt over my beard so that it stays stuffed down my collar to look presentable ...

    My track record of making side trips, each holding me back for a year or two:
    2007: Linux support
    2009: Migrating from OpenGL ~1.4 to OpenGL 2.1 (later GL ES 2)
    2010: Tired of never getting anywhere, almost abandoned the project
    2013: Changing architecture to multi-threaded for multi-core CPU support
    2015: x86-64 support (still not finished, dammit, requires FPC 3.2 released to continue)
    2016: Raspberry Pi support
    2018: Epic refactoring of my horribly dated ODBMS I created back in 2006

  4. #4
    Ah yes.. strings! i've done migration of my game to widestrings everywhere 2-3 years ago, i still feel some pain, but it was worth it in the end!

    Same with "epic refactoring" - after converting my project from delphi 7 to freepascal, i've done so much refactoring and consequentially simplifications - make use of operator overlading, generics and methods in records, code writes a hell lot cleaner in modern pascal

    You have interesting way of building your engine, especially that you can keep resources loaded and re-load the game logic, i assume it's done via sort of dll mechanism?
    This is my game project - Top Down City:
    http://www.pascalgamedevelopment.com...y-Topic-Reboot

    My OpenAL audio wrapper with Intelligent Source Manager to use unlimited:
    http://www.pascalgamedevelopment.com...source+manager

  5. #5
    Yes, indeed. All game logic resides in a module DLL hosted by the mother EXE. When the DLL unloads it uses the same serialization mechanism used for saving to selectively gather all asset classes (owning OpenGL handles and such) and store them into a memory stream owned by the EXE.
    The process is not as simple as it looks (because of interdependent nested assets like fbos and their textures I have the assets loaded from the save "devour" their counterparts received from the mother taking over their handles) but I had it working almost perfectly before the rehaul.

    Generics, turns out I have invented them as well, back when fpc 1.x didn't have dynamic arrays yet but already had modern classes. Worked via tricky includes and preprocessor. I plan to re-do the static remains of those classes, still used everywhere, using real generics. But that is a secondary task.

    What I was going to do before rehauling my ODBMS was replacing the old, horribly awkward module switching GUI in the EXE with the standard GUI that is available to the DLL, making them into a specialized module DLL of their own thus leaving the EXE a dumb container only able to render console. And because I interrupted that task half-way in October 2018, I forgot what I was going to do and where I stopped. Now my engine finally runs... rendering a blank screen and not responding to inputs.
    Where do I start... Boo-hoo-hoo...

  6. #6
    During debugging, encountered a grievous documentation error:
    Code:
    function TChepersyMemoryManagerChunk.Alloc: pointer;
    var 
      i, k: integer;
      j: cardinal;
      m: ptruint;
    begin
      i:= 0;
      // the mask bits of non-valid indexes are pre-set to 1, see the constructor
      for i:= 0 to High(f_AllocMask) do begin
        m:= not f_AllocMask[i];
    
        if m = 0 then continue; 
        // https://www.freepascal.org/docs-html/current/rtl/system/bsfdword.html
        // incorrectly states that BsfDWord returns 255 if no bits are set
        // while in fact it returns 0! (at least in fp 2.6.4 it does)
            j:= {$ifdef cpu64}BsfQWord( {$else}BsfDWord( {$endif} m );
    addlog(' i=%0, j=%1, k=%2, mask=%3',[i,j,k,pointer(f_AllocMask[i])])    ;
    //    if j < 255 then begin
          k:= (i * 8 * sizeof(pointer)) + j;
          Assert((k >= f_IdxLow) and (k <= f_IdxHigh)
                , 'TChepersyMemoryManagerChunk.Alloc: index ' + IntToStr(k) 
                + ' is out of bounds (' + IntToStr(f_IdxLow) + ',' 
                + IntToStr(f_IdxHigh) + ')');
          Inc(f_AllocCount);
          Dec(f_FreeCount);
          if f_FreeCount = 0 then CpsMemoryManager.OnChunkBecomingFull(Self);
          f_AllocMask[i]:= f_AllocMask[i] or (ptruint(1) shl j); 
          Exit(pointer(ptruint(Self) + ptruint(k) * f_Size));
    //    end;
      end;
      Die(MI_ERROR_PROGRAMMER_NO_BAKA, [
                               'TChepersyMemoryManagerChunk.Alloc algorithm fail']);
    end;
    ..aaand I'd say this is more than just twitching:

    YEEEE-HAW!

  7. #7
    elaborating:
    Code:
    program test;
    begin
      WriteLn(BsfDword(0));
      WriteLn(BsfQWord(0));
      WriteLn({$I %FPCVERSION%});
    end.
    d:chentrahmodulestests>c:FPC2.6.4bini386-win32fpc bsfdword.pas
    Free Pascal Compiler version 2.6.4 [2014/03/06] for i386
    Copyright (c) 1993-2014 by Florian Klaempfl and others
    Target OS: Win32 for i386
    Compiling bsfdword.pas
    Linking bsfdword.exe
    7 lines compiled, 0.1 sec , 25616 bytes code, 1628 bytes data

    d:chentrahmodulestests>bsfdword
    0
    4231860
    2.6.4

    d:chentrahmodulestests>c:FPC3.0.4bini386-win32fpc bsfdword.pas
    Free Pascal Compiler version 3.0.4 [2017/10/06] for i386
    Copyright (c) 1993-2017 by Florian Klaempfl and others
    Target OS: Win32 for i386
    Compiling bsfdword.pas
    Linking bsfdword.exe
    7 lines compiled, 0.1 sec, 25424 bytes code, 1252 bytes data

    d:chentrahmodulestests>bsfdword
    255
    255
    3.0.4

  8. #8
    This mighty Cheb finally wrangled unicode paths into submission!

    I've successfully ran my engine in GL ES 2 mode from a folder named D:\/人◕‿‿◕人\ while it contains ANGLE DLLs ripped from old Firefox, which are NOT unicode - i.e. they crash and burn if loaded from a path not representable in the system 8-bit encoding (CP1251 in my case).

    This is how:
    Code:
      function GetAnsiSafePath(s: TFileNameString): TFileNameString;
      {$ifndef windows}
      begin
        Result:= s;
      end;  
      {$else}
      // ASSUMING that the file name is safe anyway
      var
        u, b: UnicodeString;
        reqlen: dword;
        fn, pt: TFileNameString; 
        a: Array of UnicodeString;
        i: integer;
      begin
        if Length(s) = 0 then Exit(s);
        fn:= ExtractFileName(s);
        // first, optimize the path correcting slashes and collapsing all '\..\'
        pt:= OptiPath(ExtractFilePath(s));
        u:= FileNameToUnicode(pt);
        if IsPathAnsiSafe(u) then Exit(s);
        a:= Explode('\', u);
        u:= a[0] + '\'; // assuming its the drive letter, not checking
        for i:= 1 to High(a) - 1 do begin
          u+= a[i] + '\';
          if IsPathAnsiSafe(u) then continue;
    
          reqlen:= GetShortPathNameW(@u[1], nil, 0); //msdn sayd NOT INCLUDING
            // the terminating #0 but then where does that extra space
            // come from?
          if reqlen = 0 then begin
            GetLastError; // clear the error message
            Exit(s);
          end;
          SetLength(b, reqlen); // automatically creates extra space for the terminating zero
          SetLength(b, GetShortPathNameW(@u[1], @b[1], reqlen + 1));
          if Length(b) = 0 then Exit(s);
          u:= b;
        end;
        Result:= UnicodeToFileName(u) + fn;
      end;
      {$endif}
    , where OptiPath is my custom path parser that collapses relative paths containing '\..\', UnicodeToFileName and so on are from my chtonic patch for fpc where TFileNameString = Utf8String even in Windows (I don't use lazarus libraries, implemented everything on my own) and IsPathAnsiSafe is this:
    Code:
    function IsPathAnsiSafe(u: UnicodeString): boolean;
    {$ifndef windows}
    begin Result:= Yes end;
    {$else windows}
    var 
      a: UnicodeString;
      b: AnsiString;
      i: integer;
      res: longbool;
      ac: AnsiChar;
    begin
      if Length(u) < 1 then Exit(Yes);
      
      res:= false;
      i:= WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK or WC_DISCARDNS, 
                                               @u[1], length(u), nil, 0, nil, nil);
      if i < 1 then Exit(Yes); // graceful degradation
      SetLength(b, i);
      ac:= #7;
      WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK or WC_DISCARDNS,
                                            @u[1], length(u), @b[1], i, @ac, @res);
      Result:= Yes;
      for i:= 1 to Length(b) do
        if ord(b[i]) < 32 then begin
          Result:= No;
          break;
        end; 
    end;
    {$endif windows}
    Aaaand, it worked!
    Loading d:\84BC~1\3rdparty\ANGLE\win32\libGLESv2.dll...Ok, d:\84BC~1\3rdparty\ANGLE\win32\libGLESv2.dll
    Loading d:\84BC~1\3rdparty\ANGLE\win32\libEGL.dll...Ok, d:\84BC~1\3rdparty\ANGLE\win32\libEGL.dll
    Loading the procedure addresses from the GL ES DLL ...
    glActiveTexture() at 5F1742C0h in d:\84BC~1\3rdparty\ANGLE\win32\libGLESv2.dll
    glAttachShader() at 5F1742C5h in d:\84BC~1\3rdparty\ANGLE\win32\libGLESv2.dll
    glBindAttribLocation() at 5F1742CAh in d:\84BC~1\3rdparty\ANGLE\win32\libGLESv2.dll
    glBindBuffer() at 5F1742CFh in d:\84BC~1\3rdparty\ANGLE\win32\libGLESv2.dll
    P.S. I had unhealthy obsession with redefining true and false as "Yes" and "No". I still follow this in my engine as it became standard.

    P.P.S. This was a big problem in WinXP as it used user's name for that user's home directory. Use one non-unicode character and suddenly lots of software crash on you. Including anything compiled in fpc.
    I'm not sure if later windozes resolved that.
    Last edited by Chebmaster; 14-01-2020 at 03:30 PM.

  9. #9
    Trying trunk 3.3.1 aka the larva of 3.2.

    I was glad to see the exception handling in threads created by a DLL is working now out of the box. Interestingly, it seems having no conflicts with existing Win32 SEH handlers (as each driver DLL you load likely installs its own, I tested and confirmed that) but RTL 3.3.1 doesn't install its own handler.

    I wasn't able to figure it out *how* exception handling of 3.3.1 works, it's a black box for me now.

    Test:
    Code:
    program thrtesta;
    {$mode objfpc}
    {$apptype console}
    {$longstrings on}
    uses
    {$ifdef unix}
      cthreads,
    {$endif}
      SysUtils,
      Classes
      {$ifdef unix}
        , dl
      {$else}
        , windows
      {$endif}
      ;
    type
      TTestThread = class(TThread)
      protected
        procedure Execute; override;
      end;
    
      procedure TTestThread.Execute;
      begin
        WriteLn('> A');
        try
          byte(nil^):= 0;
        except
          WriteLn('exe thread ID=',GetCurrentThreadId()
            ,' catch: ',(ExceptObject as Exception).Message);
        end;
        WriteLn('< A');
      end;
     
      function PCharToString(P: PAnsiChar): Utf8String;
      var
        i: integer;
        p2: PAnsiChar;
      begin
        if not Assigned(p) then Result:= ''
        else begin
          p2:= p;
          i:= 0;
          While p2^ <> #0 do begin
            inc(p2);
            inc(i);
          end;
          SetLength(Result, i);
          MOVE(p^, Result[1], i);
        end;
      end; 
    
    var
      t: TTestThread;
      dllhandle: {$ifdef unix} pointer {$else} THandle {$endif};
      mypath: string;
      {$ifdef unix}
        ufn, upn: Utf8String;
      {$else}
        wfn: UnicodeString;
        wpn: AnsiString;
      {$endif}
      thrproc: procedure; cdecl = nil;
     
    begin
      WriteLn('the exe is built using fpc '
        ,{$I %FPCVERSION%},'/',{$I %FPCTARGETOS%},'/',{$I %FPCTARGETCPU%});
      WriteLn('main thread ID=', GetCurrentTHreadId());         
      t := TTestThread.Create(False);
      WriteLn('exe thread created'); 
      try
        t.WaitFor;
      finally
        t.Free;
      end;
      WriteLn('exe thread terminated');
      WriteLn('loading the DLL...');
      mypath:= ExtractFilePath(ParamStr(0))
        + {$ifdef unix} 'libthrtestb.so' {$else} 'thrtestb.dll' {$endif};
      WriteLn('path is ', mypath);
      {$ifdef unix}
        ufn:= mypath; 
        dllhandle:= dlopen(PAnsiChar(nu8), RTLD_NOW);
        if not Assigned(DLL) then begin
          WriteLn('failed to load: ',PCharToString(dlerror()));
          Halt(0);
        end;
        upn:= 'thrproc';
        pointer(thrproc):= dlsym(dllhandle, PAnsiChar(upn));
      {$else}
        wfn:= mypath;
        SetLastError(0);
        dllhandle:= LoadLibraryW(PUcs2Char(wfn));
        if dllhandle = 0 then begin
          WriteLn('failed to load.');
          Halt(0);
        end;
        wpn:= 'thrproc';
        pointer(thrproc):= windows.GetProcAddress(dllhandle, PAnsiChar(wpn));
      {$endif}
      if not Assigned(pointer(thrproc)) then begin
        WriteLn('failed to load the procedure.');
        Halt(0);
      end;
    
      WriteLn('invoking the dll...');
      try
        thrproc;
      except
        WriteLn('exe thread ID=',GetCurrentThreadId()
          ,' catch: ',(ExceptObject as Exception).Message);
      end;
      WriteLn('unloading the dll...');
      {$ifdef unix}
        dlClose(dllhandle);
      {$else}
        FreeLibrary(dllhandle);
      {$endif}
      WriteLn('done.');
    end.
    Code:
    library thrtestb;
    {$mode objfpc}
    {$apptype console}
    {$longstrings on}
    uses
    {$ifdef unix}
      cthreads,
    {$endif}
      SysUtils,
      Classes;
    type
      TTestThread = class(TThread)
      protected
        procedure Execute; override;
      end;
    
      procedure TTestThread.Execute;
      begin
        WriteLn('> X');
        try
          WriteLn('> Y');
          try
            WriteLn('> Z');
            try
              byte(nil^):= 0;
            except
              WriteLn('dll thread ID=',GetCurrentThreadId()
                ,' catch in block Z: ',(ExceptObject as Exception).Message);
            end;
            WriteLn('< Z');
          except
            WriteLn('dll thread ID=',GetCurrentThreadId()
              ,' catch in block Y: ',(ExceptObject as Exception).Message);
          end;
          WriteLn('< Y');
        except
          WriteLn('dll thread ID=',GetCurrentThreadId()
            ,' catch in block X: ',(ExceptObject as Exception).Message);
        end;
        WriteLn('< X');
      end;
    
      procedure MyMainProc; cdecl;
      var t: TThread;
      begin
        WriteLn('the dll is built using fpc '
          ,{$I %FPCVERSION%},'/',{$I %FPCTARGETOS%},'/',{$I %FPCTARGETCPU%});
        try
          t := TTestThread.Create(False);
          WriteLn('dll thread created'); 
          try
            t.WaitFor;
          finally
            t.Free;
          end;
        except
          WriteLn('dll thread ID=',GetCurrentThreadId()
            ,' catch in main proc: ',(ExceptObject as Exception).Message);
        end;
        WriteLn('the dll is done.')
      end;
     
    exports
      MyMainProc name 'thrproc';
    begin
    // do nothing.
    // The initialization sections DO NOT WORK in Linux for DLLs. And never will.
    end.
    Still a crash-to-desktop in 3.0.4:
    the exe is built using fpc 3.0.4/Win32/i386
    main thread ID=6444
    exe thread created
    > A
    exe thread ID=6736 catch: Access violation
    < A
    exe thread terminated
    loading the DLL...
    path is d:chentrahmodulesteststhrtestb.dll
    invoking the dll...
    the dll is built using fpc 3.0.4/Win32/i386
    dll thread created
    > X
    > Y
    > Z
    An unhandled exception occurred at $1000165F:
    EAccessViolation: Access violation
    Works perfectly in 3.3.1, the 17-year old bug finally closed:
    the exe is built using fpc 2.6.4/Win32/i386
    main thread ID=780
    exe thread created
    > A
    exe thread ID=6232 catch: Access violation
    < A
    exe thread terminated
    loading the DLL...
    path is d:chentrahmodulesteststhrtestb.dll
    invoking the dll...
    the dll is built using fpc 3.3.1/Win32/i386
    dll thread created
    > X
    > Y
    > Z
    dll thread ID=6076 catch in block Z: Access violation
    < Z
    < Y
    < X
    the dll is done.
    unloading the dll...
    done.
    On the engine front, I postpone trying 3.3.1 to autumn 2020 (sadly also postponing x86-64 support that requires it) and concentrate on making my engine usable using 3.0.4.
    The reason is difference in RTTI that necessitates making adjustments to my persistency system (and debugging them, dammit!)


    It will be ready in a month or two, with a simple asteroids game so that I could finally shave after all those years.
    Last edited by Chebmaster; 22-03-2020 at 09:24 AM.

  10. #10
    I give too little time to this project.

    Made my engine compile and work using 3.2.0-rc1, check. Pain in the butt with strings, you *cannot* use the "+" operator to concatenate them, they are guaranteed to be wrecked horribly.

    Now bringing Win98 support back. Restored a lot of neglected/commented out code designed to work in 98: it has so many vital WinAPI functions missing! I made a dedicated conditional CGE_PLATFORM_HAS_WINDOWS98 and wrap any relevant code in it. Because that would only work if the mother exe and the module DLL are compiled using fpc 2.6.4 (because lots of WinAPI functions have to be loaded manually, a program built using fpc 3 would crash in Win98 due to unsatisfied dependencies). So I have to support fpc 2.6.4 for all eternity patching all of its bugs by myself (the lack of Unicode support, the inability to catch AVs in a DLL, the incorrectly working functions like BsfDword - I have successfully patched them all in my engine. Took a lot of effort and dedication).

    The mother EXE for Win32 will always be compiled using fpc 2.6.4, with optimization set to pentium 3/x87 to keep compatibility with Athlon XP/Pentium III. The module DLL will normally be compiled using fpc 3.2/pentium m/sse2 but there will always be a separate legacy version built using fpc 2.6.4/pentium 3/x87

    Scavenged me a Radeon 9800 Pro 128Mb AGP (it has a molex power connector, LOL). Downloaded specific Catalyst for it to work in 98. Now I only have to install it into that machine and actually install Win98 on it.

    Examples of dirty tricks Win98 support requires:
    Code:
      function TWinApiFramework.GetScreenRect(fullscreen: boolean): TWindowManagerRect;
      var
        rc: TRect;
        Monitor: HMONITOR;
        mi: TMonitorInfo;
      begin
        {$ifdef CGE_PLATFORM_HAS_WINDOWS98}
         if (Mother^.State.OS in [ostWin98, OstWin2k]) 
           and (not Assigned(GetMonitorInfo) or not Assigned(MonitorFromRect))
         then with Result do begin
           //Use legacy method fow windozes older than XP
           left:= 0;
           top := 0;
           width:= GetSystemMetrics(SM_CXSCREEN);
           height:= GetSystemMetrics(SM_CYSCREEN);
           //Assuming that taskbar is at the bottom and is 28 pixels high:
           if not Fullscreen then Height-= 28;
         end
         else 
        {$endif}
        begin
          if windowhandle = 0 then begin
            //Not created yet. Use default monitor.
             with rc do begin
               left:= 0;
               top:= 0;
               right:= 1;
               bottom:= 1;
             end;
             Monitor:= MonitorFromRect(rc, MONITOR_DEFAULTTOPRIMARY);
          end else begin
            //get monitor from window
            GetWindowRect(windowhandle, rc);
            Monitor:= MonitorFromRect(rc, MONITOR_DEFAULTTONEAREST);
          end;
          mi.cbSize:= sizeof(mi);
          GetMonitorInfo(Monitor, @mi);
          if fullscreen
            then rc:= mi.rcMonitor
            else rc:= mi.rcWork;
          Result.left:= rc.left;
          Result.top:= rc.top;
          Result.width:= rc.right - rc.left;
          Result.height:= rc.bottom - rc.top;
        end;
      end;

Page 1 of 5 123 ... LastLast

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •