|
Conradin Beginner
Joined: 07 May 2017 Posts: 10
|
Posted: Mon Jul 25, 2022 3:10 pm
How to prevent duplicate variable creation? |
I have a simple that's just supposed to block until cmud reads a prompt from the mud. It's defined as a simple alias/trigger pair, like so:
Code: |
// #alias await-prompt
#var awaiting_prompt 1 0 Data
$start = @timestamp()
#while (@awaiting_prompt == 1 && ((@timestamp() - $start) < 2)) {
#wait 100
} |
Code: |
// #trigger ^>
#var awaiting_prompt 0 0 Data |
Mostly this works well, but every once in a while (every 10 or 20 hours maybe?) an extra awaiting_prompt variable gets created at the root. This breaks the script, since @awaiting_prompt refers to the new variable at root instead of the one that the trigger updates. Nothing in the package except for that one trigger, alias, and variable contains the string "awaiting_prompt".
What might be going on, and how can I prevent it? I started using #var instead of normal variable-assignment syntax to make sure the value would always get written to the right place in the right class, but it doesn't seem to help. I've had the same problem in other contexts before, usually involving alarms or classes being enabled and disabled in multiple threads, but even in this simple situation it still happens from time to time. |
|
|
|
shalimar GURU
Joined: 04 Aug 2002 Posts: 4690 Location: Pensacola, FL, USA
|
Posted: Mon Jul 25, 2022 7:51 pm |
My guess is that the class/folder you house the variable in is getting disabled somewhere.
This forces the next definition to resolve at the root because there is no active variable with the name.
The easy fix would be to keep the variable at root and delete the other copy. Or at the minimum, make sure it's in a class/folder that never gets disabled. |
|
_________________ Discord: Shalimarwildcat |
|
|
|
Conradin Beginner
Joined: 07 May 2017 Posts: 10
|
Posted: Mon Jul 25, 2022 8:42 pm |
I don't think that's it. The class it's in is called Data, and it contains a bunch of core functionality for my scripts like hit point tracking, room contents parsing, and so on. If it were disabled a dozen different things would immediately break, not just this. Also, the string "#T-" only appears in one spot in the whole package, with a constant argument.
Also, I just tested it and when you given #var an explicit class argument, it will set the variable in the specified class, creating a new variable if necessary, even if that class is disabled, so long as it can't resolve any other variables with that name.
Is it possible that cmud's internal stack can become corrupted under some rare circumstances? And if so, do we know what triggers it, so that I can stop doing it? I've had other problems that seem to suggest that: my long-running scripts occasionally fail with implausible errors like "argument still on stack" and "operator requires two operands". |
|
|
|
shalimar GURU
Joined: 04 Aug 2002 Posts: 4690 Location: Pensacola, FL, USA
|
Posted: Mon Jul 25, 2022 11:47 pm |
It is possible, though I am unaware of the circumstances for such off the top of my head.
I know editing multiple rooms on the map simultaneously can mess up the creation of the next room, but that's not really applicable here. |
|
_________________ Discord: Shalimarwildcat |
|
|
|
chaossdragon Apprentice
Joined: 09 Apr 2008 Posts: 168
|
Posted: Tue Jul 26, 2022 12:05 am |
out of curiousity, how long do you leave cmud running at any given time? Do you save and close the program periodically? I have noticed that dupe vars and mis-firing scripts will occur with variably increased frequency the longer it's left running.
To the point that I'll get an exception error pop-up and may suffer a roll back if I made any substantial changes and neglected to save/restart (at least once a day) |
|
|
|
shalimar GURU
Joined: 04 Aug 2002 Posts: 4690 Location: Pensacola, FL, USA
|
Posted: Tue Jul 26, 2022 5:20 am |
Typically I have it up for running till I finally give in to the windows update requests.
A week or three at a time.
It was a bit more frequent when creating and coding new scripts (several times a day even), but once you get things working, it is very stable. |
|
_________________ Discord: Shalimarwildcat |
|
|
|
Conradin Beginner
Joined: 07 May 2017 Posts: 10
|
Posted: Tue Jul 26, 2022 9:54 am |
I do feel like things get worse when cmud has been up for a long time, or when Windows has; I try not to let cmud run for more than 2 or 3 hours at a time. But this isn't always good enough, sometimes it misbehaves immediately on startup.
I thought I had a workaround: just use #unvar to unset the variable before I set it, and change the reading code to tolerate reading "" values from time to time. But this doesn't work: cmud becomes unstable after just a few minutes of variable setting and unsetting, and AFAICT it isn't possible to unset a var if and only if it's defined at the root; `#unvar @var` will unset what variables it can find, and adding an explicit class argument of "" doesn't change that. |
|
|
|
shalimar GURU
Joined: 04 Aug 2002 Posts: 4690 Location: Pensacola, FL, USA
|
Posted: Tue Jul 26, 2022 10:14 am |
That also depends on your system.
Have you looked into using a dbVar?
With that, you could house all of your utility variables in one master variable...at root. |
|
_________________ Discord: Shalimarwildcat |
|
|
|
miegorengman Wanderer
Joined: 05 Sep 2008 Posts: 51
|
Posted: Tue Jul 26, 2022 5:19 pm |
Conradin would you be willing to share the variable update/creation script you have made? I am personally in the process of trying to learn how to build that and it might also be helpful to trace back the potential cause.
thank you much |
|
|
|
Conradin Beginner
Joined: 07 May 2017 Posts: 10
|
Posted: Wed Jul 27, 2022 7:30 am |
@shalimar Thanks for your replies. I'm not sure what you mean; what depends on my system? I'm on windows 10, cmud 3.34. As for dbvars, I do use them in several places, for example to keep track of buffs and inventory item counts, but I find that they sometimes get corrupted when I write to them (adding blank entries that map "" to "", or deleting keys). For things like buff tracking I can work around that, because I can always regenerate the buff data from scratch by querying the MUD, but for storing internal variables it seems pretty dangerous.
@miegorengmen Here's the XML for the variable-setting function I wrote; is that what you meant?
Code: |
<func name="set_var" id="677">
<value>#unvar %eval($var) ""
#var %eval($var) %eval($val) _nodef %eval($class)</value>
<arglist>$var, $val, $class</arglist>
</func> |
The %eval()s are necessary; #var will expand @global_vars but won't expand %local_vars unless you explicitly ask for it. But again, this didn't work well: calling it every few seconds was enough to crash cmud after just a couple of minutes, along with a bunch of other weird behavior. |
|
|
|
shalimar GURU
Joined: 04 Aug 2002 Posts: 4690 Location: Pensacola, FL, USA
|
Posted: Wed Jul 27, 2022 6:35 pm |
More that the amount of RAM your system has can drastically affect the performance of laggy scripts.
Do you have a permanent prompt trigger?
Seems like you could do away with that unfortunate-looking #WHILE waiting for a prompt to finish the cleanup by having a dedicated trigger to do it for you.
#TR {^>} {#IF (@awaiting_prompt) {awaiting_prompt=0}} {} {prompt} |
|
_________________ Discord: Shalimarwildcat |
|
|
|
Conradin Beginner
Joined: 07 May 2017 Posts: 10
|
Posted: Thu Jul 28, 2022 7:27 am |
I have a trigger like that, see the first post, but I still need a blocking loop in the alias. The usecase is, the MUD doesn't allow a lot of command stacking: if I send four commands in a row, `a;b;c;d`, the last two commands will be ignored. So my strategy is to wait for the MUD to send back a prompt, like so:
Code: |
a
await-prompt
b
await-prompt
c
await-prompt
d |
So: await-prompt sets the flag @awaiting_prompt, and then blocks until the trigger unsets it again.
The trigger sets awaiting_prompt unconditionally, which now that I think about it is probably wrong: it avoids a race condition where await-prompt is called at about the same time a prompt comes in, but leads to a lot more calls to #var, which is probably where the bug is happening. So I'll just put an #IF in the trigger and see if that helps.
Is there a way to get cmud to use more RAM? I have 8GB, but it never uses more than a few hundred megabytes, and usually not even that much. |
|
|
|
shalimar GURU
Joined: 04 Aug 2002 Posts: 4690 Location: Pensacola, FL, USA
|
Posted: Thu Jul 28, 2022 11:37 am |
I think it would only be using more in those high-stress situations.
Or maybe it uses more CPU processing instead.
As for the command stacking issue, you could always set a command queue for those commands that get set to the MUD.
commandList=a|b|c|d
Then have the prompt trigger do this:
#IF (@commandList) {#EXEC %pop(commandList)} |
|
_________________ Discord: Shalimarwildcat |
|
|
|
Conradin Beginner
Joined: 07 May 2017 Posts: 10
|
Posted: Thu Jul 28, 2022 1:51 pm |
I have experimented with that kind of design in the past, but couldn't make it reliable: list variables seem to have a chance to become corrupt every time you write to them, much like DB variables do, and guarding them with a #SECTION does not help.
It's also pretty tricky to compose this kind of asynchronous design with other kinds of blocking. For example, `check-inv` is an alias that queries the MUD for my inventory and then blocks with #waitfor until it gets a line indicating that we've seen my whole inventory, while some triggers populate @inventory with items and counts. Combined with `go2` (which executes a speedwalk to a named room) and `sell-loot` (which looks at my @inventory variable to find things to sell), I can write a script to sell all my excess loot like this:
Code: |
go2 pawnshop
check-inv
sell-loot |
How might we express this asynchronously? The obvious idea is to put all three scripts in the command queue. Instead of using %push() directly, we'll add a helper function @enqueue_command() that guards the %push() with a #section. Then the go-and-sell-loot script becomes:
Code: |
#call @enqueue_command("go2 pawnshop")
#call @enqueue_command("check-inv")
#call @enqueue_command("sell-loot") |
It's kinda annoying to have to type "#call @enqueue_command()" all the time, but more importantly this doesn't work: we no longer have any guarantee that check-inv will finish before sell-loot runs, so we might not sell the right things. More promising is to run check-inv synchronously and enqueue the other two:
Code: |
#call @enqueue_command("go2 pawnshop")
check-inv
#call @enqueue_command("sell-loot") |
But this is also buggy: check-inv and go2 are now running in separate threads, so they'll send input to the mud at the same time and might saturate the typeahead limit, and then some commands won't be run.
There are more elaborate approaches that I suspect would solve the problem (it would be a big help if all the enqueued commands ran in the _same_ worker thread, for example), but I'm hesitant to build a lot on cmud's advanced features when more basic things like "writing to a list" and "setting a variable" are so unreliable. |
|
|
|
shalimar GURU
Joined: 04 Aug 2002 Posts: 4690 Location: Pensacola, FL, USA
|
Posted: Fri Jul 29, 2022 3:52 am |
This outlier situation probably has more to do with whatever caused the corruption in the first place.
Variable definitions are usually very reliable.
#SECTION and threading, in general, is one of the most elaborate and advanced concepts in CMUD, and most users have no real need for anything that complex.
The main thread is capable of handling most scripts you throw at it.
The only time I bother with it is for a laggy function like %mapquerry
P.S. %push adds things to the front of the list, which would make command order be LIFO instead of FIFO, %additem would append to the end of the list. |
|
_________________ Discord: Shalimarwildcat |
|
|
|
Conradin Beginner
Joined: 07 May 2017 Posts: 10
|
Posted: Fri Jul 29, 2022 7:43 am |
So you think there's something different about my cmud install, compared to a normal one? That would be really good news, because I could fix it, and then I wouldn't need a workaround for the behavior in the OP, I could just rely on #VAR to behave as documented. Do you have ideas of what the cause might be, or what sorts of things I should search for? I already know a few basic things, like not calling user functions without #CALL, and not using signals and events. EDIT: and not enabling/disabling classes in scripts.
|
|
|
|
shalimar GURU
Joined: 04 Aug 2002 Posts: 4690 Location: Pensacola, FL, USA
|
Posted: Fri Jul 29, 2022 10:52 pm |
Who told you that those things were bad?
There is nothing wrong with #EVENTs.
#SIGNAL is another threading command.
Enabling and disabling is also fine, so long as you aren't turning off variables you will continue defining.
As is using functions outside of a #CALL. #CALL is more a means of isolation.
None of the settings are bad, though some are more prone to unexpected results than others.
Unfortunately, the exact cause of corruption is rarely known for certain.
Though it usually stems from typos or bad syntax.
Without looking over every script you have, there is no way to know the root of your issue.
The script debugger window can help to see what is firing at any given moment.
Sometimes the only answer is to recreate from scratch.
I have had to do that a number of times, and each time my code comes out cleaner than before. |
|
_________________ Discord: Shalimarwildcat |
|
|
|
Tarn GURU
Joined: 10 Oct 2000 Posts: 873 Location: USA
|
Posted: Sat Jul 30, 2022 4:03 am |
Conradin wrote: |
So you think there's something different about my cmud install, compared to a normal one? That would be really good news, because I could fix it, and then I wouldn't need a workaround for the behavior in the OP, I could just rely on #VAR to behave as documented. Do you have ideas of what the cause might be, or what sorts of things I should search for? I already know a few basic things, like not calling user functions without #CALL, and not using signals and events. EDIT: and not enabling/disabling classes in scripts. |
I did read all of this thread.
One suggestion for a possibly cleaner design:
this is arising because you have different code accessing a variable.
When I have a need that I think is the same as what's in your original example (loop which waits for timeouts or a prompt), I prefer to add two triggers/events/what have you:
1) a single trigger on the prompt - "Do I have anything pending to do? If so, do it."
and
2) a periodic alarm - same thing: "Do I have anything I want to do as a result of this time increment elapsing?"
#WAIT forces some threading things to happen that can be hard to deal with. |
|
|
|
Conradin Beginner
Joined: 07 May 2017 Posts: 10
|
Posted: Sun Jul 31, 2022 9:31 am |
shalimar wrote: |
Who told you that those things were bad? |
Nobody told me these things were bad, those are all things I've observed. For example:
Quote: |
There is nothing wrong with #EVENTs. |
I once tried to refactor one of my mud scripts to use events. I had several characters on that mud (Lost Souls IIRC), so I wrote a generic combat script that called several "hook" aliases that were defined for each character (each character had its own class, and only one class was enabled at a time). Schematically it looked like this:
Code: |
battlestart-hook
#while (@in_combat()) {
battleround-hook
#wait 2000
}
battleover-hook |
There were other hooks too, but those 3 were used by far the most often. But not every character used every hook, and it annoyed me that I had to define empty aliases for those characters, so I decided to replace those aliases with events and use #RaiseEvent to trigger them. My combat script then became:
Code: |
#RaiseEvent BattleStart
#while (@in_combat()) {
#RaiseEvent BattleRound
#wait 2000
}
#RaiseEvent BattleOver |
Up until that change cmud had been crashing every few hours, as is usual for most muds I play. After this change it crashed every few minutes, very reliably: the mud was unplayable. I reverted all the hooks except the battle hooks, and then cmud ran fine except when I was fighting things, when it would crash regularly. I took out some of the battle hooks and it still crashed during combat, but more rarely. I took out the rest of the battle hooks, reverted everything to aliases, and things went back to normal. I never touched any of the hook code, I just converted them all from aliases to events, and back again.
I have similar stories for all the other examples I gave. If you're curious about the others feel free to ask, but this post is already too long.
Quote: |
I have had to [recreate from scratch] a number of times, and each time my code comes out cleaner than before. |
Would you be willing to share a copy of one of your mud packages? The whole thing, I mean, modulo any sensitive data. I'm curious what good cmud style looks like from your perspective, and I really want to get to the root of why we have such different experiences. |
|
Last edited by Conradin on Sun Jul 31, 2022 9:52 am; edited 2 times in total |
|
|
|
Conradin Beginner
Joined: 07 May 2017 Posts: 10
|
Posted: Sun Jul 31, 2022 9:50 am |
Tarn wrote: |
When I have a need that I think is the same as what's in your original example (loop which waits for timeouts or a prompt), I prefer to add two triggers/events/what have you:
1) a single trigger on the prompt - "Do I have anything pending to do? If so, do it."
and
2) a periodic alarm - same thing: "Do I have anything I want to do as a result of this time increment elapsing?"
#WAIT forces some threading things to happen that can be hard to deal with. |
Shalimar had this idea too, and I think it can work in cases where you only want to do asynchronous things, and where all of the asychronous things react to the same trigger (a prompt, in this case). But when you want to mix synchronous and asynchronous code, or have two different kinds of asynchrony, it gets pretty hard to make sure that everything happens in the right order. See my comment that starts "I have experimented with that kind of design in the past". If you have any large-scale scripts that use this technique (more than a thousand lines of code across the whole package, say) I'd be very interested to read them. |
|
|
|
Tarn GURU
Joined: 10 Oct 2000 Posts: 873 Location: USA
|
Posted: Mon Aug 01, 2022 1:44 am |
Conradin wrote: |
Tarn wrote: |
When I have a need that I think is the same as what's in your original example (loop which waits for timeouts or a prompt), I prefer to add two triggers/events/what have you:
1) a single trigger on the prompt - "Do I have anything pending to do? If so, do it."
and
2) a periodic alarm - same thing: "Do I have anything I want to do as a result of this time increment elapsing?"
#WAIT forces some threading things to happen that can be hard to deal with. |
Shalimar had this idea too, and I think it can work in cases where you only want to do asynchronous things, and where all of the asychronous things react to the same trigger (a prompt, in this case). But when you want to mix synchronous and asynchronous code, or have two different kinds of asynchrony, it gets pretty hard to make sure that everything happens in the right order. See my comment that starts "I have experimented with that kind of design in the past". If you have any large-scale scripts that use this technique (more than a thousand lines of code across the whole package, say) I'd be very interested to read them. |
Well, even the helpfile tells you not to use #WAIT for complicated cross thread things...
If you really want to do it that way then I'd try encasing any access (read or write) of shared variables inside a single alias or something which handles both read and write with #SECTION so that only one thread can access it at the same time. |
|
|
|
shalimar GURU
Joined: 04 Aug 2002 Posts: 4690 Location: Pensacola, FL, USA
|
Posted: Mon Aug 01, 2022 6:55 am |
Conradin wrote: |
Quote: |
I have had to [recreate from scratch] a number of times, and each time my code comes out cleaner than before. |
Would you be willing to share a copy of one of your mud packages? The whole thing, I mean, modulo any sensitive data. I'm curious what good cmud style looks like from your perspective, and I really want to get to the root of why we have such different experiences. |
I have a number of packages uploaded to the package library you can look at. |
|
_________________ Discord: Shalimarwildcat |
|
|
|
Conradin Beginner
Joined: 07 May 2017 Posts: 10
|
Posted: Mon Aug 01, 2022 9:02 am |
Tarn wrote: |
Well, even the helpfile tells you not to use #WAIT for complicated cross thread things...
If you really want to do it that way then I'd try encasing any access (read or write) of shared variables inside a single alias or something which handles both read and write with #SECTION so that only one thread can access it at the same time. |
The issue I have isn't with inconsistent reads or other normal threading issues, it's with cmud crashes and constraint violations, such as the one in the first post. Are you saying that I should expect cmud to crash if I, for example, read a variable from one thread while writing it from another without an explicit guard? Is that your experience?
shalimar wrote: |
I have a number of packages uploaded to the package library you can look at. |
I've seen some of your work there, but the packages I looked at are all pretty small. Just for context, I have about 4000 lines of zscript code in my Lost Souls package, over about 700 aliases and 400 triggers. Most of that code is not very interesting, just 20 or 30 core aliases and functions that get called in dozens of different ways, but still. I'm looking for something more of that scale, rather that isolated bits of functionality, if you're comfortable sharing. My best guess right now is that there's some pattern that I use and you don't which causes memory corruption: that would explain how you're able to keep cmud up for weeks (!!) and I can't, and why the symptoms are so idiosyncratic. |
|
|
|
shalimar GURU
Joined: 04 Aug 2002 Posts: 4690 Location: Pensacola, FL, USA
|
Posted: Tue Aug 02, 2022 12:39 pm |
I tend to break stuff down and segment it to protect from corruption.
Compartmentalization.
It effectively serves as a backup of known good code as well
Any corruption that matters will only be in my main session file.
Besides, not everyone will want every bit of functionality I decide to use.
Nor do i necessarily want to share every line of code I use.
I would be rather surprised if this all comes down to a trigger pattern.
From what you said the issue seems to stem from one of the aliases you tried to convert into an event. As that was when things became unplayable. |
|
_________________ Discord: Shalimarwildcat |
|
|
|
|
|