Graf Zahl (snippets) wrote:What do you need dynamic strings for? (...)
- implementing functions returning string values (e.g. getActorClass, getCurrentWeapon etc.
- creating string parameters for functions like SpawnSpot on the fly. (...)
store game status stuff. For this char arrays would be much more suitable. If necessary, add a function to copy a string to an array and all should be well.
The above is part of Graf Zahls response to my addition of (nigh) unlimited (and unmanaged) string support to ACS. My intention was also to provide actual automated management through an ACS-compiler, but this was (sensibly) considered an excessive effort. As Graf pointed out, we don't need real dynamic strings as anything but function input/output.
The string only needs to exist very briefly: Long enough to be consumed by a function (or transferred to an array).
Spoiler: Addition and basic idea
A "virtual string library" (ID 0x7FFF) has been added. This library fetches strings from a stack (TArray<FString>) that is cleared at the end of every tick. This stack can never lose a reference without properly disposing of it first, so there is no danger of memory leak.
Strings are added to this library at runtime, through a new value-expression-token (pardon the term) in ACC.exe. The new token shares most of its code with the statement-token "print".
In ACS the new token poses as the int valued function strparam(print-format). It is named to reinforce the idea that strings allocated here should be used directly, rather than being stored for later use.
Spoiler: No longer true
In my current build of ACS, it is an error to use strparam() as a statement; it can only be used as an expression in an assignment.
Spoiler: Inside details
Since strparam shares code with print, it uses the FString named "work" to build its strings. Now that we have a value-expression string-builder, it is possible to have multiple strings under construction at once. Support for this has been added through the addition of a static string-builder-stack.
The normal print statements do not concern themselves with the stack, but strparam puts whatever is under construction on the stack, and starts building from an empty work-string. When strparam completes, it pushes the workstring to the virtual library and pops the previous work string back in place.
If there are strings on the builder-stack at the end of ACS-processing (of one tick), the stack is cleared and an exception is raised.
Two new PCD-tokens have been defined to support this (ZDoom.exe, ACC.exe):
PCD_PUSHSTRBUILDER (called before the normal print code)
PCD_SAVESTRANDPOP (called instead of the output code)
(Saving a string means pushing its integer-representation to the stack; something is expected to take it from the stack and clean up afterwards. Normally this is a variable or parameter assignment.)
(The reason why calling "strparam" as a statement and disregarding its return-value, aside from it being a near worthless operation with predictable but undefined behaviour, is that it would require addition of PCD_SAVESTRANDPOP_VOID, not to push a value to the stack.)
Potential additions (I don't think the need will arise): PCD_POPSTRINGBUILDER, PCD_SAVESTRANDPOP_VOID
I only downloaded the ACC-source yesterday, and I don't know how to provide a good svn-diff for it. If that, or any violations of best practice I might have made, poses a problem; let us work on it. This time I think the basic concept is simple and safe.
Side note: No new save data is introduced. The dynamic strings don't live enough to become part of the saved gamestate.
Spoiler: Draft user documentation, mark 2
int strparam(string-format)
This function generates a string using the [wiki]Print[/wiki]-syntax, but instead of displaying it on the screen, it associates it with an integer value. The string will exist only until the ACS finishes processing the current tick. If you need permanent access to text from the string you can use [wiki]StrLen[/wiki] and [wiki]GetChar[/wiki] to transfer data to an array. You can later use that array in strparam(a:array) to generate an on-the-fly string for use as a function parameter.
Return value: Integer pointing to the formatted string.
global int 1:characterArray; // could contain anything, dynamic content under your control
script 2 enter
{
GiveInventory(strparam(a:characterArray), 1); // use any printable string as a parameter
}
script 3 enter
{
// you can also assign it to a variable,
// but its contents are only valid until this tick ends.
int keyObject = strparam(s:mapname,s:"key");
if (CheckInventory(keyObject))
{
TakeInventory(keyObject, 1);
}
else
{
print(s:"You require ",s:keyObject);
terminate; // after ending the script, you cannot expect the value in keyObject to point to a valid string anymore
}
ACS_Execute(120,0,10);
// ACS_Execute will not execute code during this tick.
// keyObject will not exist anymore when it starts running script 120.
delay(1); // now a tick will pass. any dynamic strings generated before this delay will be invalid once execution resumes.
/*
more code
*/
}
EDIT: A modified version has been packaged and uploaded alongside the original submission. "ZDOOM_ACC_StrParam_implicit.zip" contains an ACC project and a ZDoom diff, as well as an overview of changes to ACC.
Spoiler: Under the hood
PCD-code for pushing stringbuilder has been removed. PCD-code for saving strings has been named PCD_SAVESTRING, not to make any implications regarding stack-handling. String-builder-stack handling is now a zdoom.exe internal affair.
STRINGBUILDER_START has been defined to add any ongoing string to stack and create an empty stringbuilder.
Appears in: BeginPrint.
STRINGBUILDER_FINISH has been defined to take any string from stack, and set the string builder to "" if the stack was empty.
Appears in: SaveString, EndPrint|Bold|Log & Hudmessage(+Bold).
For any STRINGBUILDER_START there is a requirement to call STRINGBUILDER_FINISH.
Edit: Replaced all versions with the latest one. String stack handling is implicit and automatic, and strparam can be called as a statement.
You do not have the required permissions to view the files attached to this post.
Last edited by FDARI on Wed May 11, 2011 1:49 pm, edited 3 times in total.
FDARI wrote:PCD_PUSHSTRBUILDER (called before the normal print code)
PCD_SAVESTRANDPOP (called instead of the output code)
Hmm. I'll have to think about this longer, but here's my initial thoughts here: Do you actually need to manipulate the string stack directly here? All print statements (print, printbold, hudmessage, and hudmessagebold) begin with a BeginPrint instruction. At this point you logic can pretty much be "if build is not empty push to stack." Then just modify all the end instructions to pop the stack if there is something left. It would seem this would fit better into what already exists.
Making the push and pop implicit might be worth the time. The easy way to do that is to push and pop on all operations (seems wasteful for all the existing output calls that don't need it). A more complex approach is to push whenever work contains data, and pop whenever stack contains stringbuilders.
Until now there has been no need to clean up the string-builder; I think it will usually contain the result of the last print-job until you call BeginPrint. BeginPrint will not be able to distinguish between residual data and in-construction data. That means that all operations might push strings to stack this way (needlessly). That means all operations will need cleanup code.
In such a scenario we could reduce the amount of push/pop-ing by having an implicit "" at the bottom of the stack. (If stack empty, assign ""; otherwise pop.)
That, coupled with cleanup after all string generations, will make sure we don't push when it isn't necessary.
EDIT: If there is already something on stack, we can't not push the empty string to stack when we start a new one, because we are going to pop it on completion (provided that the condition for pop is that there is a string on stack).
Last edited by FDARI on Wed May 11, 2011 2:40 pm, edited 1 time in total.
OK, I think this should work for me. I think it should also be safe, albeit senseless, to ignore the return value from the function now. The only thing I can suggest is calling it something else, maybe strprint.
My strongest reason for not supporting "strparam" as a statement (unassigned function call) is that it would require an additional pcd-instruction for zdoom.exe: Add the string to the temporary library without putting the result value on the acs-value-stack. (As far as I can tell, all code that exists before this submission would exit with a stack size of 0, and I want to maintain that level of orderliness.)
I can add the instruction PCD_SAVESTRING_VOID to perform this operation.
As for the function name: Anything will to. SPRINT would be meaningful to some. I still want the name to remind you, however, that the string is not a lasting reference.
FDARI wrote:My strongest reason for not supporting "strparam" as a statement (unassigned function call) is that it would require an additional pcd-instruction for zdoom.exe: Add the string to the temporary library without putting the result value on the acs-value-stack. (As far as I can tell, all code that exists before this submission would exit with a stack size of 0, and I want to maintain that level of orderliness.)
Actually there exists a Drop instruction which could be called to remove the value from the stack.
That looks a little like a feature suggestion, there. I think we have all the bits and pieces required for a strcopy (one new PCD and a new token to ACC). Because it needs to reference a whole array (its last dimension) it can probably not use a standard function definition, and must be built as a token (print and strparam are tokens in ACC). ZDoom-side another PCD is required.
The hard part is (hopefully) done by now.
Let's discuss STRCPY.
Minimum information: One string (int), one array reference.
Possible information: Source start index. Destination start index. Number of characters to copy.
Required return information: None
Possible return information: Result state (failure, success, or more specific information), or number of characters copied.
Handling of special cases:
* Number of characters to copy exceeds actual number of characters. Proposition: Copy actual number of characters. Return indicates success.
* Number of characters to copy exceeds available space in destination. Proposition: Skip or Fill array. Return indicates incomplete operation/failure.
This function should probably be designed with ACC++ in mind, since ACC++ will perform much array/string work. I'm very interested in what those developers want.
Possible definition of standard ACC.EXE function/token:
bool strcpy(str sourceString, array destination [, int sourceIndex [, int destinationIndex [, int count]]]);
Return true if data is copied, false if data could not be copied.
Given that we have 3 pcodes for printing arrays:
printmapchararray(index, offset)
printworldchararray(index, offset)
printglobalchararray(index, offset)
We would probably need to do the same for strcpy. One thing to keep in mind is that ACS does not have multi-dimensional arrays at the VM level (as far as I can tell anyways). So with that in mind:
void strcpy(array destination, str source[, int srcoffset [, int count]])
`array destination` would actually push two values the array index and the offset. The type of array would be determined by the instruction. strcpyglobalarray, strcpyworldarray, or strcpymaparray.
I don't think a return value is needed, since it should never return a failure for global/world arrays (they are ever expanding). For map arrays it should print an error to the console (and possibly terminate the script).
Blzut3 wrote: (and possibly terminate the script).
Better not.
FraggleScript does that for any kind of error and the problems caused by such aborts are often worse than letting the script continue, even when testing.
Any function that can legally* fail should be able to inform the caller of the outcome. ACS offers no error management, so the only viable information path is the return value. I really do want the return value to give an unambigous message about the outcome of that function. The ACS-coder is responsible for caring and testing the outcome.
From the above, 3 pcodes seems likely. Means to specify exact starting points may already be present for all three types (as opposed to an array + an offset in two different parameters). Internally in zdoom.exe we may represent it as destination, source, srcoffset, count. To the user, however, I don't want the starting point in the array to be a part of the array-parameter, as it is confusing. I would have ACC.EXE work the offset into the array-specification.
*By legally I mean: Not caused by an actual bug in acc.exe or zdoom.exe.