Question on XP Dev - RDATA and DLScan (Do I use both for sub-entity selection?)

edited December 2021 in Macros and XPs

I have been browsing through the XP tutorials located at https://rpgmaps.profantasy.com/xp-tutorials/

I have a question that involves a case where I want to have the user select a path, explode that path, and then perform operations on each of the members of that path. At this point I'm just trying to get a feel for the general requirements of the commands I will have to call.

I see parts within the tutorials that would assist me in having the user select the path entity; my question is whether I can easily access each of the members of the Path and hold them in an array, and then explode the Path and still be able to perform operations on the array of entities?

If so I'm looking at something along the lines of...

// Request Path from User

// Assign requested Path sub-entities to array

// Explode Path

// Iterate array of sub-entities and perform operations on each


I have both a general and a more specific question...

Does this flow make sense or does anyone foresee some problems I may need to be aware of?

More specifically, How do I get the members of the Path? Is there some access directly from the Path or from the Explode command, or will I have to figure it out second-hand in some way by walking the drawing list? (similar to the example of finding the symbols on a specific sheet?)

Tagged:

Comments

  • MonsenMonsen Administrator 🖼️ 46 images Cartographer

    The important thing to remember about dealing with a path is that it is a primitive, it isn't a container for other entities like a sheet, symbol or multipoly is. As such, you cannot access the line segments in the path, because it doesn't contain any, it just contains nodes. So there isn't a way to get a reference to these before exploding. You can figure out what the lines will be by looking at the nodes in the path, but there aren't lines to get references to. (Also, changes in the drawing list can cause things to shift around in memory, so holding a pointer to an entity in the drawing list that was acquired prior to a modification may very well not be valid anymore.)


    I haven't tried exploding from code myself, but I guess that would be the EExplode function. That function does have a parameter of type EXPPROC which looks like a callback function, maybe that could be of some use in determining the new lines, but I haven't really figured out how to use it properly.

    DoubleDouble
  • edited December 2021

    Thanks, I'll do my best to play with it a bit later tonight and report back.

    I figure worst case is that I'll have to gather up a list of entities, (maybe by ID if I can't trust the pointer?) explode the heck out of the path, and then look at the list of entities again to gather references to any that are not within my original list, (and of the correct type)

    Either that or maybe I will simulate the exploding by using the nodes, not quite sure what I'll decide works best.

  • Quick update; I couldn't figure out much about EXPPROC but otherwise I've been able to use EEXPLODE fine just setting that second argument as NULL. Figuring I'll have to do a lot of entity filtering

  • I'm too stubborn for my own good. I have the callback activating but I have no idea what sort of pENTREC is being passed to me.

    (not quite sure how to format below as code) I have a breakpoint to investigate the pENTREC but not quite sure what I should be looking for to identify what I can do with it.

    DWORD XPCALL ExplodeCallback(pENTREC pEntRec)

    {

    return 0;

    }

    Monsen
  • MonsenMonsen Administrator 🖼️ 46 images Cartographer

    Look for EType in CStuff in the entity record first. That tells you what kind of entity this is. You'll find the ID's in _ESTRUC.H.

    My issue from the very brief look I gave that one was that I didn't seem to actually get valid entities at all, just uninitialized memory. Which may be due to a mistake on my side, or perhaps something that needs to be done that I don't know about, there is no documentation about that call.

    DoubleDouble
  • I'm kind of wondering if it was just an object for passing the CStuff about the User's line width, color, etc, I did notice the EType seemed to be 0 as far as I could tell, which indicates no point data going by _ESTRUC.H

  • jslaytonjslayton Moderator, ProFantasy Mapmaker

    The documentation for EExplode states:

    ;	={==============================================================
    ;	EExplode - Explode Entity
    ;		Optional service (cannot be DFScan callback)
    ;	---------------------------------------------------------------
    ;	Entry:	ESI -> Entity record
    ;		EDI -> Process exploded component entity proc
    ;	Exit:	Carry set if not supported, else clear
    ;		and process callback was called.
    ;	---------------------------------------------------------------
    ;	If EDI callback adrs = 0 then :
    ;		a) Append component entity, b) draw appended entity.
    ;	---------------------------------------------------------------
    ;	If it is not 0, then just build component entities but do not
    ;	append or draw them, and pass the address of the entity in ESI
    ;	to the callback proc.
    ;	=}==============================================================
    

    The C wrapper for that routine (most likely the one that you're calling) is defined as:

    BOOL _stdcall EExplode(pENTREC pEntRec,EXPPROC pExpProc);
    

    which would put pEntRec as ESI and pExpProc as EDI. FastCAD is still mostly raw ASM code internally and doesn't often follow C compiler conventions. There are C wrappers for most of the code elements that your program can call, but not all of the callbacks from within those code elements back to your code have been correctly wrapped. This is one of those cases where the callback is doing something that's not as specified in the EXPPROC definition. The pENTREC parameter (a pointer to a discriminated union whose type is identified by the EType field in the union) is passed in ESI, not on the stack as the EXPPROC parameter would indicate. You'll need to unpack the pENTREC parameter yourself using something like

    DWORD XPCALL ExplodeCallback(pENTREC pEntRec)
    {
        __asm mov	pEntRec, esi
        // now safe to do work
        return 0;
    }
    

    Any time you see what appears to be a garbled pointer in what you would expect to be a valid C parameter, there's a good chance that a meaningful pointer is in ESI, EDI, or EBX (and/or that there are parameters passed on the 8087 stack).

    I'll put this on the bug list, but I will tell you that's it's probably not going to be a particularly high priority fix and fixing it will take a while because and be a bit risky because there are quite a few callbacks scattered around for this particular operation.

    DoubleDoubleMonsen
  • jslaytonjslayton Moderator, ProFantasy Mapmaker

    As a follow-up, please let me know if you get that to work or just abandon the problem altogether.

    I'm curious if my diagnosis of the problem is correctly matching the observed results for the problem you're trying to solve (there is an EO_EXPLODE message handler for each entity type and I didn't check all of the types). You can see from the presence of the parameter on the EXPPROC C definition that it was intended to be updated, but the asm code never was updated, which could have been for many reasons, including that no calls for EExplode ever provide a C callback to EExplode.

    In case it's helpful, the default handler (pExpProc = 0) looks like:

    		INVOKE	DLApnd,NULL,esi		; esi = partial entity for callback
    		mov	esi,eax
    		SVC	EDraw		
    		ret
    


    JimP
  • MonsenMonsen Administrator 🖼️ 46 images Cartographer

    Tried a test implementation here, but getting into some issues. The callback is only called once, even though I should get 3 entities back from the entity I explode. (Using the default handler correctly leaves 3 entities in place). The entity I do get back seems correct.

    EExplode then returns, and the program crashes with an access violation. Do I need to do anything else before returning from the callback?

  • edited December 2021

    I didn't get to work on it yesterday but I can confirm from prior experience that mine also tends to crash with an access violation upon returning the '0' from the callback (at least I assume that's the point of failure).

    I would have guessed that the entity you get back would be a grouping of entities so I'm surprised it's only 1.

  • jslaytonjslayton Moderator, ProFantasy Mapmaker

    It occurs to me that the callback definition shouldn't have a parameter because it's a _stdcall routine and manages its own parameters via registers. What it's doing right now is discarding the 4 parameter bytes on return, which are actually the return address; it then returns to some unrelated address, which is a bad thing. The callback definition should take void and return int:

    DWORD XPCALL ExplodeCallback(void)
    {
        pENTREC pEntRec;
        __asm mov	pEntRec, esi
        // now safe to do work
         return 0;
    }
    

    You'll need to cast the callback as EXPPROC at the point of use.

  • MonsenMonsen Administrator 🖼️ 46 images Cartographer

    Ah, thanks, that does it.

    An interesting observation is that if you have a callback method, the original entity isn't erased automatically, you need to handle that yourself, while if you don't have a callback, it is handled automatically.

  • edited December 2021

    Making great progress based off discussion so far.

    Another thing I've run into is that I'm trying to request a (float or double) number from the user (they will have to type it in); is RD_Real4 the best RDATA.dtype to accomplish this? the comment just mentions, "real value"

    I'm noticing that the number coming across is not what I'm typing in... the DWORD is coming through as something massive, 1075838976 for instance when I type in 2.5

    C++ is not a language I'm very great with so apologies if it's something simple. It looks like maybe it's a bit-shift or type-cast issue but I couldn't quite match it up with anything that made sense to me.

  • jslaytonjslayton Moderator, ProFantasy Mapmaker

    The value in the rdata structure is defined as a dword*, but it really ought to be void*. If you want a float, cast the pointer from float* to dword* as the rdata parameter, then use the float* on return so that you're using the float.

    There are lots of different kinds of float values you can request (real, distance, angle, and so on). Pick the one that best matches your problem.

  • edited December 2021

    I believe I have completed my first function. You maybe could see where I was going with this but I wanted to create a TAPERPATH command. So I programmed a command which will require:

    1. The User to select a path
    2. The User to select a point near the end of the path that will be the narrower end
    3. The User to type in the minimum line width
    4. The User to type in the maximum line width

    The command will then explode the path and taper each line gradually.

    Something I have observed is that you may find it best to change the fill style to solid if the path is currently set to hollow (before running the command) otherwise you have to try and select all the little pieces if you want to change it after. I do set an undo point so if you forget you can just undo the command.

    The XP is attached within a zip file. If you want to use it you should unzip it to your install directory (default C:\Program Files (x86)\ProFantasy\CC3Plus), and to activate it type in TAPERPATH


    I will follow this comment with some code for review, maybe I could learn something. Feel free to give me any feedback for how the command could improve.

    JimP
  • edited December 2021

    Code for Review:

    Most of the lifting is done with these two functions, but I have the full file attached as well to go more in-depth.

    void XPCALL ExplodePath(const int Result, int Result2, int Result3) {
    	UNREFERENCED_PARAMETER(Result2);
    	UNREFERENCED_PARAMETER(Result3);
    	if (Result != X_OK) { CmdEnd(); return; }
    
    	// Determine if direction of Path should increase or decrease as it progresses
    	int nodeCount = selectedPath->Path.Path.Count;
    	float increDist = Dist2P(directionPoint.x, directionPoint.y, selectedPath->Path.Path.Nodes[0].x, selectedPath->Path.Path.Nodes[0].y);
    	float decrDist = Dist2P(directionPoint.x, directionPoint.y, selectedPath->Path.Path.Nodes[nodeCount-1].x, selectedPath->Path.Path.Nodes[nodeCount-1].y);
    	if (increDist < decrDist)
    	{
    		index = 0;
    		indexCount = 1;
    	}
    	else
    	{
    		index = nodeCount - 1;
    		indexCount = -1;
    	}
    
    	//Actually do the explode, add elements in callback, and erase
    	MarkUndoAdd();
    	bool result = EExplode(selectedPath, (EXPPROC)ExplodeCallback);
    	DLErase(selectedPath);
    
    	CmdEnd();
    }
    
    DWORD XPCALL ExplodeCallback(void)
    {
    	pENTREC pEntRec;
    	__asm mov	pEntRec, esi
    	// some math for determining line width
    	float sizeIncr = (maximumWidth - minimumWidth) / (selectedPath->Path.Path.Count-1);
    	pEntRec->CStuff.LWidth = (sizeIncr * index) + minimumWidth;
    	//append objects to drawing list, draw them, and do the index counting to progress line width increase or decrease
    	DLApnd(NULL, pEntRec);
    	EDraw(pEntRec);
    	index+=indexCount;
    
    	return 0;
    }
    
Sign In or Register to comment.