Jump to content

Willicious

Members
  • Content Count

    138
  • Joined

  • Last visited

  • Days Won

    1

Everything posted by Willicious

  1. My Delphi program has a somewhat complex level selection menu which loads various items including graphics, level information and a treeview for selecting packs and levels. Up until recently, the menu has started to hang and occasionally become unresponsive, occasionally requiring closing the program altogether using Task Manager. My question is, how would I go about locating the source of the problem? Something as generic as a program hang which only happens occasionally doesn’t really point towards anything specific, but the chances are that it is something specific which is causing the issue. I’m happy to share the entire .pas file for the level select menu (about 1700 lines of code) if anyone thinks it necessary, but I wondered if there might be a specific way to find out where loading bottlenecks might be happening, or how to isolate whatever is causing the program to hang. I use RAD Studio 10.4 (the program doesn’t compile at all on 11 or higher). Suggestions welcome, thank you.
  2. Willicious

    How to debug a Not Responding program element

    Well, this is very strange. I went to try madExcept again today, and now the Level Select can't even be accessed: With madExcept disabled, the program runs fine and the memory leak is gone. Not sure what's happening here...
  3. Willicious

    How to debug a Not Responding program element

    OK, so Anders' solution gets rid of the built-in resource leak dialog👍, but madExcept still gives us this:
  4. Willicious

    How to debug a Not Responding program element

    Ah yes, I get it now. "Clear;" is called from Clone (as JonRobertson has also pointed out). But, it isn't in the call stack (not directly, anyway). So, (and this is possibly one of those noobish questions), without prior knowledge of where "Clear" is being called, how can the call stack be used to trace this by itself? If the answer is that it can't, that's of course fine - at least we know what we're looking for, so we can search for it initially and obtain the prior knowledge needed to interpret the call stack usefully. Got it. Thanks to you both for all the explanations of what's going on, it's made everything much clearer. I'll go ahead and implement Anders' suggested change and report back what happens.
  5. Willicious

    How to debug a Not Responding program element

    Good shout. Here's the call stack up to the breakpoint: It's still not 100% clear how we got to the "Free" call, though... I've highlighted the most recent thing that isn't in System.Generics
  6. Willicious

    How to debug a Not Responding program element

    OK... this is a bit of a can of worms. fPrimaryAnimation doesn't get freed, and PrimaryAnimation (property referring to fPrimaryAnimation) doesn't get freed. NewInstance and NewAnim also don't get freed. I honestly don't know where to start. The joys of working on someone else's code 🤨
  7. Willicious

    How to debug a Not Responding program element

    Should it? Bear in mind I'm very new to fixing memory leaks: nothing is obvious to me. NewAnim doesn't appear to be freed anywhere. However, my attempts to free it have so far only led to program crashes, or the memory leak persisting. Here's TGadgetAnimation's destructor: destructor TGadgetAnimation.Destroy; begin Dec(fTempBitmapUsageCount); if (fTempBitmapUsageCount = 0) then fTempBitmap.Free; fTriggers.Free; fSourceImage.Free; fSourceImageMasked.Free; inherited; end; If I shift-click "inherited" here, we get this empty procedure, in System. So, I assume that either Delphi just somehow 'knows' what to do here, or it does nothing: destructor TObject.Destroy; begin end; TGadgetMetaAccessor doesn't have a destructor, but TGadgetMetaInfo does. Maybe (i.e. I very much don't know for sure) "Animations.Free" is responsible for dispensing with all created TGadgetAnimations: destructor TGadgetMetaInfo.Destroy; var i: Integer; begin for i := 0 to ALIGNMENT_COUNT-1 do begin fVariableInfo[i].Animations.Free; fInterfaces[i].Free; end; inherited; end; Shift-clicking "inherited" there leads to the same empty procedure in System. So yes, I'm as baffled as you are.
  8. Willicious

    How to debug a Not Responding program element

    Here's the last 2 items in the call stack (I assume that only the most recent lines in the call stack are relevant?): procedure TGadgetAnimations.AddPrimary(aAnimation: TGadgetAnimation); begin Add(aAnimation); if fPrimaryAnimation <> nil then fPrimaryAnimation.fPrimary := false; <---------------------------------------- this is line 935 fPrimaryAnimation := aAnimation; aAnimation.fPrimary := true; end; procedure TGadgetAnimations.Clone(aSrc: TGadgetAnimations); var i: Integer; NewAnim: TGadgetAnimation; begin Clear; NewAnim := TGadgetAnimation.Create(aSrc.PrimaryAnimation.fMainObjectWidth, aSrc.PrimaryAnimation.fMainObjectHeight); NewAnim.Clone(aSrc.PrimaryAnimation); AddPrimary(NewAnim); <------------------------------------------------- this is line 949 for i := 0 to aSrc.Count-1 do begin if aSrc.Items[i].Primary then Continue; NewAnim := TGadgetAnimation.Create(aSrc.Items[i].fMainObjectWidth, aSrc.Items[i].fMainObjectHeight); NewAnim.Clone(aSrc.Items[i]); Add(NewAnim); end; SortByZIndex; end; This points to fPrimaryAnimation being the problem. Maybe this has already been freed and then not re-created for use here (best guess from what you've said)? Question is, which is the best action to take: 1) Free fPrimaryAnimation later 2) Re-create and re-free fPrimaryAnimation 3) Something else?
  9. Willicious

    How to debug a Not Responding program element

    Thanks, I've given this a try. It crashed on attempting to load a level with Pickup skill objects. Here's the call stack: Does this give us any more clues as to what to target? Should I FreeAndNil everything in TGadgetAnimations just to be sure?
  10. Willicious

    How to debug a Not Responding program element

    Here's the regular memory leak dialog:
  11. Willicious

    How to debug a Not Responding program element

    Struggling to make sense of this one. I've found a resource which isn't being freed ("NewAnim", a TGadgetAnimation instance), but all attempts to free it result in either the program not running at all, or the resource leak persisting anyway: procedure TGadgetAnimation.Load(aCollection, aPiece: String; aSegment: TParserSection; aTheme: TNeoTheme); var BaseTrigger: TGadgetAnimationTrigger; LoadPath: String; S: String; NeedUpscale: Boolean; Bitmaps: TBitmaps; i: Integer; Info: TUpscaleInfo; begin Clear; fFrameCount := aSegment.LineNumeric['frames']; fName := UpperCase(aSegment.LineTrimString['name']); fColor := UpperCase(aSegment.LineTrimString['color']); if LeftStr(fName, 1) <> '*' then begin if GameParams.HighResolution then LoadPath := AppPath + SFStyles + aCollection + SFPiecesObjectsHighRes + aPiece else LoadPath := AppPath + SFStyles + aCollection + SFPiecesObjects + aPiece; if fName <> '' then LoadPath := LoadPath + '_' + fName; // For backwards-compatible or simply unnamed primaries LoadPath := LoadPath + '.png'; if GameParams.HighResolution and not FileExists(LoadPath) then begin LoadPath := AppPath + SFStyles + aCollection + SFPiecesObjects + aPiece; if fName <> '' then LoadPath := LoadPath + '_' + fName; // For backwards-compatible or simply unnamed primaries LoadPath := LoadPath + '.png'; NeedUpscale := true; end else NeedUpscale := false; fHorizontalStrip := aSegment.Line['horizontal_strip'] <> nil; TPngInterface.LoadPngFile(LoadPath, fSourceImage); if fHorizontalStrip then begin fWidth := fSourceImage.Width div fFrameCount; fHeight := fSourceImage.Height; end else begin fWidth := fSourceImage.Width; fHeight := fSourceImage.Height div fFrameCount; end; if NeedUpscale then begin Bitmaps := MakeFrameBitmaps(true); Info := PieceManager.GetUpscaleInfo(aCollection + ':' + aPiece, rkGadget); for i := 0 to Bitmaps.Count-1 do Upscale(Bitmaps[i], Info.Settings); CombineBitmaps(Bitmaps); end else if GameParams.HighResolution then begin fWidth := fWidth div 2; fHeight := fHeight div 2; end; end else begin fHorizontalStrip := false; if Lowercase(fName) = '*blank' then begin fWidth := aSegment.LineNumeric['WIDTH']; fHeight := aSegment.LineNumeric['HEIGHT']; // Preserve previously-loaded frame count. fSourceImage.SetSize(fWidth * ResMod, fHeight * ResMod * fFrameCount); fSourceImage.Clear(0); end else begin // Fallback behaviour. Could mean it's unrecognized, or handled elsewhere (eg. "*PICKUP"). fSourceImage.SetSize(ResMod, ResMod); fSourceImage.Clear(0); fFrameCount := 1; fWidth := 1; fHeight := 1; end; end; // Only set fPrimary by TGadgetAnimations if fPrimary and (aSegment.Line['z_index'] = nil) then fZIndex := 1 else fZIndex := aSegment.LineNumeric['z_index']; if Uppercase(aSegment.LineTrimString['initial_frame']) = 'RANDOM' then fStartFrameIndex := -1 else fStartFrameIndex := aSegment.LineNumeric['initial_frame']; if fPrimary then begin fMainObjectWidth := fWidth; fMainObjectHeight := fHeight; end; fOffsetX := aSegment.LineNumeric['offset_x']; fOffsetY := aSegment.LineNumeric['offset_y']; fCutTop := aSegment.LineNumeric['nine_slice_top']; fCutRight := aSegment.LineNumeric['nine_slice_right']; fCutBottom := aSegment.LineNumeric['nine_slice_bottom']; fCutLeft := aSegment.LineNumeric['nine_slice_left']; BaseTrigger := TGadgetAnimationTrigger.Create; S := Lowercase(aSegment.LineTrimString['state']); if (S = 'pause') then BaseTrigger.fState := gasPause else if (S = 'stop') then BaseTrigger.fState := gasStop else if (S = 'looptozero') then BaseTrigger.fState := gasLoopToZero else if (S = 'matchphysics') then BaseTrigger.fState := gasMatchPrimary else if (aSegment.Line['hide'] <> nil) then BaseTrigger.fState := gasPause else BaseTrigger.fState := gasPlay; if (aSegment.Line['hide'] = nil) then BaseTrigger.fVisible := true else BaseTrigger.fVisible := false; fTriggers.Add(BaseTrigger); if fPrimary then begin // Some properties are overridden/hardcoded for primary BaseTrigger.fState := gasPause; // Physics control the current frame BaseTrigger.fVisible := true; // Never hide the primary - if it's needed as an effect, make the graphic blank end else begin // If NOT primary - load triggers aSegment.DoForEachSection('trigger', procedure(aSec: TParserSection; const aCount: Integer) var NewTrigger: TGadgetAnimationTrigger; begin NewTrigger := TGadgetAnimationTrigger.Create; NewTrigger.Load(aSec); fTriggers.Add(NewTrigger); end ); end; fNeedRemask := true; fMaskColor := $FFFFFFFF; end; procedure TGadgetMetaInfo.Load(aCollection,aPiece: String; aTheme: TNeoTheme); var Parser: TParser; Sec: TParserSection; GadgetAccessor: TGadgetMetaAccessor; NewAnim: TGadgetAnimation; PrimaryWidth: Integer; begin fGS := Lowercase(aCollection); fPiece := Lowercase(aPiece); GadgetAccessor := GetInterface(false, false, false); Parser := TParser.Create; try ClearImages; if not DirectoryExists(AppPath + SFStyles + aCollection + SFPiecesObjects) then raise Exception.Create('TMetaObject.Load: Collection "' + aCollection + '" does not exist or does not have objects. (' + aPiece + ')'); SetCurrentDir(AppPath + SFStyles + aCollection + SFPiecesObjects); Parser.LoadFromFile(aPiece + '.nxmo'); Sec := Parser.MainSection; // Trigger effects if Lowercase(Sec.LineTrimString['effect']) = 'exit' then fTriggerEffect := DOM_EXIT; if Lowercase(Sec.LineTrimString['effect']) = 'forceleft' then fTriggerEffect := DOM_FORCELEFT; if Lowercase(Sec.LineTrimString['effect']) = 'forceright' then fTriggerEffect := DOM_FORCERIGHT; if Lowercase(Sec.LineTrimString['effect']) = 'trap' then fTriggerEffect := DOM_TRAP; if Lowercase(Sec.LineTrimString['effect']) = 'water' then fTriggerEffect := DOM_WATER; if Lowercase(Sec.LineTrimString['effect']) = 'fire' then fTriggerEffect := DOM_FIRE; if Lowercase(Sec.LineTrimString['effect']) = 'onewayleft' then fTriggerEffect := DOM_ONEWAYLEFT; if Lowercase(Sec.LineTrimString['effect']) = 'onewayright' then fTriggerEffect := DOM_ONEWAYRIGHT; if Lowercase(Sec.LineTrimString['effect']) = 'teleporter' then fTriggerEffect := DOM_TELEPORT; if Lowercase(Sec.LineTrimString['effect']) = 'receiver' then fTriggerEffect := DOM_RECEIVER; if Lowercase(Sec.LineTrimString['effect']) = 'pickupskill' then fTriggerEffect := DOM_PICKUP; if Lowercase(Sec.LineTrimString['effect']) = 'lockedexit' then fTriggerEffect := DOM_LOCKEXIT; if Lowercase(Sec.LineTrimString['effect']) = 'unlockbutton' then fTriggerEffect := DOM_BUTTON; if Lowercase(Sec.LineTrimString['effect']) = 'collectible' then fTriggerEffect := DOM_COLLECTIBLE; if Lowercase(Sec.LineTrimString['effect']) = 'onewaydown' then fTriggerEffect := DOM_ONEWAYDOWN; if Lowercase(Sec.LineTrimString['effect']) = 'updraft' then fTriggerEffect := DOM_UPDRAFT; if Lowercase(Sec.LineTrimString['effect']) = 'splitter' then fTriggerEffect := DOM_SPLITTER; if Lowercase(Sec.LineTrimString['effect']) = 'entrance' then fTriggerEffect := DOM_WINDOW; if Lowercase(Sec.LineTrimString['effect']) = 'antisplatpad' then fTriggerEffect := DOM_NOSPLAT; if Lowercase(Sec.LineTrimString['effect']) = 'splatpad' then fTriggerEffect := DOM_SPLAT; if Lowercase(Sec.LineTrimString['effect']) = 'decoration' then fTriggerEffect := DOM_DECORATION; if Lowercase(Sec.LineTrimString['effect']) = 'traponce' then fTriggerEffect := DOM_TRAPONCE; if Lowercase(Sec.LineTrimString['effect']) = 'onewayup' then fTriggerEffect := DOM_ONEWAYUP; if Lowercase(Sec.LineTrimString['effect']) = 'animation' then fTriggerEffect := DOM_ANIMATION; if Lowercase(Sec.LineTrimString['effect']) = 'animationonce' then fTriggerEffect := DOM_ANIMONCE; if Lowercase(Sec.LineTrimString['effect']) = 'blasticine' then fTriggerEffect := DOM_BLASTICINE; if Lowercase(Sec.LineTrimString['effect']) = 'vinewater' then fTriggerEffect := DOM_VINEWATER; if Lowercase(Sec.LineTrimString['effect']) = 'poison' then fTriggerEffect := DOM_POISON; if Lowercase(Sec.LineTrimString['effect']) = 'lava' then fTriggerEffect := DOM_LAVA; if Lowercase(Sec.LineTrimString['effect']) = 'radiation' then fTriggerEffect := DOM_RADIATION; if Lowercase(Sec.LineTrimString['effect']) = 'slowfreeze' then fTriggerEffect := DOM_SLOWFREEZE; if (Lowercase(Sec.LineTrimString['effect']) = 'decoration') or (Lowercase(Sec.LineTrimString['effect']) = 'paint') then fTriggerEffect := DOM_DECORATION; if Sec.Section['PRIMARY_ANIMATION'] = nil then begin if LastWarningStyle <> fGS then begin ShowMessage('Gadget ' + fGS + ':' + fPiece + ' is in pre-12.7 format. Please update your copy of this style, or if up to date, ask the style creator to fix.'); LastWarningStyle := fGS; end; raise Exception.Create('Gadget ' + fGS + ':' + fPiece + ' is in pre-12.7 format. Please update your copy of this style, or if up to date, ask the style creator to fix.'); end; NewAnim := TGadgetAnimation.Create(0, 0); GadgetAccessor.Animations.AddPrimary(NewAnim); NewAnim.Load(aCollection, aPiece, Sec.Section['PRIMARY_ANIMATION'], aTheme); fFrameCount := NewAnim.FrameCount; PrimaryWidth := NewAnim.Width; // Used later Sec.DoForEachSection('ANIMATION', procedure (aSection: TParserSection; const aIteration: Integer) begin NewAnim := TGadgetAnimation.Create(GadgetAccessor.Animations.PrimaryAnimation.Width, GadgetAccessor.Animations.PrimaryAnimation.Height); GadgetAccessor.Animations.Add(NewAnim); NewAnim.Load(aCollection, aPiece, aSection, aTheme); end ); GadgetAccessor.Animations.SortByZIndex; GadgetAccessor.TriggerLeft := Sec.LineNumeric['trigger_x']; GadgetAccessor.TriggerTop := Sec.LineNumeric['trigger_y']; GadgetAccessor.TriggerWidth := Sec.LineNumeric['trigger_width']; GadgetAccessor.TriggerHeight := Sec.LineNumeric['trigger_height']; GadgetAccessor.DefaultWidth := Sec.LineNumeric['default_width']; GadgetAccessor.DefaultHeight := Sec.LineNumeric['default_height']; GadgetAccessor.DigitX := Sec.LineNumericDefault['digit_x', PrimaryWidth div 2]; GadgetAccessor.DigitY := Sec.LineNumericDefault['digit_y', -6]; GadgetAccessor.ExitMarkerX := Sec.LineNumeric['exit_marker_x']; GadgetAccessor.ExitMarkerY := Sec.LineNumeric['exit_marker_y']; if LeftStr(Lowercase(Sec.LineTrimString['digit_alignment']), 1) = 'l' then GadgetAccessor.DigitAlign := -1 else if LeftStr(Lowercase(Sec.LineTrimString['digit_alignment']), 1) = 'r' then GadgetAccessor.DigitAlign := 1 else GadgetAccessor.DigitAlign := 0; fDigitMinLength := Sec.LineNumericDefault['digit_length', 1]; if Sec.Line['sound_activate'] = nil then fSoundActivate := Sec.LineTrimString['sound'] else fSoundActivate := Sec.LineTrimString['sound_activate']; fSoundExhaust := Sec.LineTrimString['sound_exhaust']; fKeyFrame := Sec.LineNumeric['key_frame']; // This is almost purely a physics property, so should not go under animations if Sec.Line['resize_both'] <> nil then begin GadgetAccessor.CanResizeHorizontal := true; GadgetAccessor.CanResizeVertical := true; end else begin GadgetAccessor.CanResizeHorizontal := Sec.Line['resize_horizontal'] <> nil; GadgetAccessor.CanResizeVertical := Sec.Line['resize_vertical'] <> nil; end; if fTriggerEffect in [DOM_NONE, DOM_DECORATION] then // No trigger area begin GadgetAccessor.TriggerWidth := 0; GadgetAccessor.TriggerHeight := 0; end; if fTriggerEffect in [DOM_RECEIVER, DOM_WINDOW] then // Trigger point only begin GadgetAccessor.TriggerWidth := 1; GadgetAccessor.TriggerHeight := 1; end; finally Parser.Free; end; end;
  12. Willicious

    How to debug a Not Responding program element

    Thanks for this. I'll give it a try - should come in handy for future memory leaks. Can it also be used for overflow/range checking as well?
  13. Willicious

    How to debug a Not Responding program element

    @Anders Melander Thanks for the advice, I installed madExcept again and it generated the following leak report: The problem was in TRenderer.DrawProjectileShadow (shown in the call stack), in which an instance of TProjectile was being created and not freed. I've now fixed this. madExcept is super useful then, no doubt. I did however uninstall it again after use because I noticed significant lag when running SLX with it enabled, and it also caused some access violation error dialogs to popup. I wonder whether this may be because I installed a newer version of madExcept which is intended to work with Delphi 12 and I'm still on Delphi 10.4 - It might be worth trying a previous version next time; the dev leaves all versions up on the site, helpfully.
  14. Willicious

    How to debug a Not Responding program element

    Got one more memory leak that's showed up and I'd like to have a go at fixing it myself. It's narrowed down to being a problem with TProjectile, which doesn't have a destructor. However, I implemented a destructor and this doesn't fix the memory leak, so... more investigation needed. I'm having some problems getting FastMM set up. Managed all but one step: I tried running the program anyway and as far as I can tell, nothing happens. Does it open a dialog or spit out a text file? Or something else?
  15. Willicious

    MAP2PDB - Profiling with VTune

    Thanks 🙂
  16. Willicious

    MAP2PDB - Profiling with VTune

    Replying again because I forgot to notify
  17. Willicious

    MAP2PDB - Profiling with VTune

    Hi, can anyone help me make sense of the following: In RAD 10.4.2, where can I find project linker options? And, do these expressly give the option to "output a detailed map file"? As in, from a command line? From within RAD? Do I need to type exactly what's here, or replace with a specific filename? So, I'm guessing this is done by running VTune whilst the application is running? Perhaps the other two steps are what link the project, so... all I need to do is run VTune? Oh, also - how can I get VTune?
  18. Willicious

    How to debug a Not Responding program element

    I'm not sure exactly how to do this. It was my (and, from what you've been saying, your) understanding that the program already did cache the loaded info, but it turns out it doesn't (at least not to any noticeable extent). I think it has to reload everything even in the same session (i.e. with SLX still open, but Level Select being closed and then re-opened).
  19. Willicious

    How to debug a Not Responding program element

    OK... I went ahead and: 1) Merged LoadIcons and LoadNodeLabels into InitializeTreeview 2) Removed the call to LoadNodeLabels in SetInfo 3) Moved the call to LoadIcons to DisplayLevelInfo (still called via SetInfo, but at least it's now in the right place!) 4) Removed the OnExpanded event handler And WOW! The difference is night and day. It takes much longer to load the Level Select initially (expected, and I'll do a progress bar for that), but once open it's super snappy and responsive, and doesn't bug out when switching between packs any more 🎉 Thanks for your help in narrowing down what was causing the delays. I clearly need to install VTune (is there a free/budget version?) as that displays exactly what's causing the delays very clearly, so it will no doubt come in very handy for other features of the program. The only thing I'd probably say is that it might be somewhat frustrating to use the slower-loading Level Select when only wanting to switch quickly between levels (e.g. for testing purposes). It might be worth doing a "Quick Select" version which only displays the treeview and nothing else; this would likely be the default. Then, a button for "Show Level Info" could go ahead and load the full version. I'll see what the feedback is after releasing this slower-loading but quicker-interfacing version of the Level Select. I'd say that pretty much wraps up this topic as resolved, but it would be good to continue the discussion around general optimization. Can the admins split the posts regarding range & overflow checking into a new topic? Happy to continue that discussion here if not. ---------------------- EDIT: After playing around with it for a bit, I think the (much!) slower loading time is too much of a cost for the slightly snappier treeview. Clicking into Level Select is done quite a lot, especially for testing purposes, and it gets old quick; I can see why it was set up the way it is (i.e. loading node labels only when actually needed). There may be other opportunities for optimization though; I'll take a look at the VTune diagram and see what's possible. Also, I've learned a ton via this thread, so thank you again!
  20. Willicious

    How to debug a Not Responding program element

    From what I can tell, none of the loading is done from the level select treeview. It's all done in LemLevel and LemNeoLevelPack and then the parsed data is merely accessed from LevelSelect. But, if that's the case, then what's causing the slow response times? It's very difficult to narrow it down to any one particular thing without guidance, hence this thread. EDIT: It's possible that, by accessing the data from LevelSelect, that causes it to be parsed in LemLevel. I'm not sure which way around it is.
  21. Willicious

    How to debug a Not Responding program element

    Yeah, it's not always the best resource for programming, but it can sometimes help with quick solutions (e.g. syntax errors) and can actually help with what you're suggesting (i.e. breaking a block of code down to analyze what it does). It absolutely still requires basic knowledge to be able to make good use of its output though. I'm not a complete beginner by now, and I do understand way more about how things work than I did this time last year (for example, I know the difference between functions, methods, variables, constants, types, classes, and what these are all for). I made my first enum type a couple of weeks ago, for example, and it was satisfying to get it right and have it optimise the code I was working on. That was done with the guidance of another programmer as well. I've been very lucky to have lots of help, and I hope I can one day provide the same for someone else. I generally do take this approach. It's more difficult with some things than others. For range check errors, from what I understand it's if something falls outside the specified bounds, for example: (i = 1 to 10) If I then somehow = 11, that would cause a range check error...?
  22. Willicious

    How to debug a Not Responding program element

    OK, thanks. It's good to have that confirmed. I'll look at ways to cache the data for later quick loading. The data that can be changed whilst the same instance of the Level Select dialog is open is level records and talisman completion data, which is all user-specific and parsed from a single text file, so this shouldn't be too problematic to re-load on the fly, as long as the actual baseline pack/level info itself is pre-loaded. When you say "optimisations in place that would prevent this from working", what do you mean by "this"? Fair question. The answer is simply because it will likely take me many months to bring the alternative UI into some semblance of usability. I'll need to learn how to load fonts and display them at the correct size for the size of the Window (or load the letters as bitmaps, and then have to increase the size of the base window so that the font can be more hi-res). Along with many other challenges, of course. So, I'd like to optiise the existing Level Select dialog in the meantime. That way, it's better sooner, and if the challenges of a brand new screen prove to be too large, I'll at least know more about how the existing one works and what I can do to optimise it. Also, I'd like to know how to cache info anyway; it may come in useful for other program elements. I'll add an option to the config menu to clear program cache, and make sure to clear it when a new copy is installed. I need to learn how to do it, though.
  23. Willicious

    How to debug a Not Responding program element

    A Google search for "integer overflow delphi" brought up the answer of using data types that are large enough to contain all values that may possibly be computed and stored in them, and a few responses suggested Int64. ChatGPT seemed to confirm this by suggesting that I change all integer types in the FadeOut procedure to Int64 instead. I went ahead and made this change, and still got the integer overflow error. So, I then applied the fix suggested by Kas Ob. (this one:) if GetTickCount > EndTickCount then // prevent integer overflow Break; And, this fixed the error. So, congratulation Kas Ob! What this has done is confirm to me that asking a person, and giving my specific case, is vastly more preferable to generic Googling and even using AI tools. How can I possibly have known that this would fix the issue? The best developer tools on the planet didn't even help solve the problem. In the end, it took a more experienced programmer applying their existing knowledge to the specific issue I was encountering. As a hobbyist with limited time to spend programming, how can I acquire that knowledge? We now have a range check error. Again, I tried googling "range check error delphi" and asking ChatGPT for a solution, neither has produced anything which fixes the issue: procedure TRenderer.ApplyRemovedTerrain(X, Y, W, H: Integer); var PhysicsArrPtr, TerrLayerArrPtr: PColor32Array; cx, cy: Integer; MapWidth: Integer; // Width of the total PhysicsMap begin // This has two applications: // - Removing all non-solid pixels from rlTerrain (possibly created by blending) // - Removed pixels from PhysicsMap copied to rlTerrain (called when applying a mask from LemGame via RenderInterface) PhysicsArrPtr := PhysicsMap.Bits; TerrLayerArrPtr := fLayers[rlTerrain].Bits; MapWidth := PhysicsMap.Width; for cy := Y to (Y+H-1) do begin if cy < 0 then Continue; if cy >= PhysicsMap.Height then Break; for cx := X to (X+W-1) do begin if cx < 0 then Continue; if cx >= MapWidth then Break; if PhysicsArrPtr[cy * MapWidth + cx] and PM_SOLID = 0 then begin if GameParams.HighResolution then begin TerrLayerArrPtr[(cy * MapWidth * 4) + (cx * 2)] := 0; TerrLayerArrPtr[(cy * MapWidth * 4) + (cx * 2) + 1] := 0; <---------------------- this line here TerrLayerArrPtr[((cy * 2) + 1) * (MapWidth * 2) + (cx * 2)] := 0; TerrLayerArrPtr[((cy * 2) + 1) * (MapWidth * 2) + (cx * 2) + 1] := 0; end else TerrLayerArrPtr[cy * MapWidth + cx] := 0; end; end; end; end; My question here would be: what exactly should I be Googling for, and how will I know if what I've found is helpful? If the answer is: "by testing out every single possible thing I find from a generic Google search", that's not exactly encouraging. Stack Overflow, in particular, is full of unspecific answers which usually require an existing level of knowledge and experience to understand in the first place, let alone put into practice for testing. Don't get me wrong, I've been using Google searches, this Forum and various AI tools over the past year to get to where I am now with SuperLemmix, so I can absolutely use these resources effectively, up to a point. When it comes to not actually knowing what I'm supposed to be searching for, or how to know whether the answer is at least relevant if not also correct, this is where I probably need a bit more specific help/mentoring. I've considered paying for lessons many times, because ultimately these are the biggest barriers to progress.
  24. Willicious

    How to debug a Not Responding program element

    Yes, let's get the thread back on track. The main thing I need help with here is fixing the slow-responding level select menu & treeview - the fixes that have been applied so far have helped for sure, but haven't eliminated the slow response times. I'm considering adding a progress bar, but if I do this then I'd want to load all information to the treeview at the start, rather than adding some when nodes are clicked. Node clicking should, then, access pre-loaded info (if this is at all possible). Please bear in mind, those that have offered suggestions so far, that I need step-by-step instructions if possible. If you say something like "just use a 3rd party treeview", that means nothing to me without instructions as to where to get it and how to intregrate it into the project. I understand that not everyone has time to type out instructions to a newbie though, so... it's fine if you don't, but chances are I probably won't get around to implementing your suggestion. Regarding general optimization though, it'd be nice to get some help with what to do with Overflow & Range checks. At present, they show a popup and point to a code area (this is great!), but I don't really know what to do from there.
  25. Willicious

    How to debug a Not Responding program element

    Thanks for the fix, I applied it and it fixed both of the leaks! I likely wouldn't have spotted this either, because on the surface it did originally appear to be the case that the cursors were being freed correctly. I also enabled Overflow and Range checking; so far, this Integer overflow is reported: procedure TGameBaseScreen.FadeOut; var RemainingTime: integer; OldRemainingTime: integer; EndTickCount: Cardinal; const MAX_TIME = 320; // mS begin EndTickCount := GetTickCount + MAX_TIME; OldRemainingTime := 0; RemainingTime := MAX_TIME; ScreenImg.Bitmap.DrawMode := dmBlend; // So MasterAlpha is used to draw the bitmap while (RemainingTime >= 0) do begin if (RemainingTime <> OldRemainingTime) then begin ScreenImg.Bitmap.MasterAlpha := MulDiv(255, RemainingTime, MAX_TIME); ScreenImg.Update; OldRemainingTime := RemainingTime; end else Sleep(1); RemainingTime := EndTickCount - GetTickCount; <--------------------------------------- this line here end; Application.ProcessMessages; end;
×