TextDrawDestroy (a workaround/upgrade?)
#1

I was working with some textdraws today and found out that textdraw IDs start at 0 and increment onward. Now some players have been complaining that their textdraws get "bugged" or "mixed up".

Now here is how textdraw IDs work:

pawn Code:
textdraw1 = TextDrawCreate(...);
textdraw2 = TextDrawCreate(...);
Now textdraw1's ID would be 0 and textdraw2's ID would be 1; that went well, but now when you're ready to destroy a textdraw you DIDN'T created this is what happens:

pawn Code:
new Text:site[2],Text:forum[2];
//remember: variables are assigned a null value of 0 when the're created.

public OnPlayerConnect(playerid)
{
    /*site textdraw is the first textdraw created, hence it's ID, 0. The next created textdraw would have
      an ID of 1 but the next textdraw "forum[playerid]" wasn't created, so it's value is still 0.
    */

    site[playerid] = TextDrawCreate(0.0,0.0,"Visit our webstie at www.website.com");
    //forum[playerid] = TextDrawCreate(0.0,0.0,"Visit our webstie at www.website.com"); NEVER CREATED*
    //now the scripter FORGOT to create the textdraw for the "forum" variable.
    return 1;
}

//Scripter testing his "hidesite" command, result: YAY am the best scripter :) it hid the site textdraw :)
//Scripter: Am gonna test the /hidefourm command now :D
COMMAND:hidesite(playerid, params[])
{
    TextDrawDestroy(site[playerid]);//NOTE: the value of the "site" variable is 0.
    //therefore it should distroy textdraw 0 (site textdraw)
    SendClientMessage(playerid,Grey,"You've hidden the site textdraw.");
    return 1;
}

//scripter testing his "hideforum" command, result: WTF?! It hid my site textdraw.
COMMAND:hideforum(playerid, params[])
{
    TextDrawDestroy(forum[playerid]);//NOTE: the value of the "forum" variable is 0.
    //therefore it would distroy textdraw 0 (site) NOT textdraw 1 (forum - which was never created)
    SendClientMessage(playerid,Grey,"You've hidden the forum textdraw.");
    return 1;
}

//Now I just made a simple function which would somewhat circumvent the bug/glitch, except with textdraw 0.
stock TextDrawDestroyEx(Text:text)
{
    if(_:text > 0)
    {
        TextDrawDestroy(text);
    }
}
See, the "TextDrawDestroy" function doesn't check if the textdraw was actually created or not, it just destroys it based on the ID. When using the function above, make sure you're not using ONE textdraw because it wouldn't destroy that ONE textdraw (because the ID of that ONE textdraw is 0 and it only checks for IDs 1 or more) so in that case, to be safe, only use "TextDrawDestroy" function to destroy textdraws with ID 0 (you can know if it's ID 0 because it would be the first textdraw you create)

Here's an example:

pawn Code:
new td[MAX_PLAYERS][4];

public OnPlayerConnect(playerid)
{
    td[playerid][0] = TextDrawCreate(0.0,0.0,"Welcome");//first textdraw created* ID 0
    td[playerid][1] = TextDrawCreate(0.0,1.0,"to");//second textdraw created. ID 1
    td[playerid][2] = TextDrawCreate(0.0,2.0,"our");//third textdraw created. ID 2
    td[playerid][3] = TextDrawCreate(0.0,3.0,"server!");//fouth textdraw created. ID 3
    return 1;
}

public OnPlayerDisconnect(playerid)
{
    TextDrawDestroy(td[playerid][0]);//This function would only be used for the first textdraw created*
    TextDrawDestroyEx(td[playerid][1]);
    /*
      Let's say you forgot to create the "td[playerid][1]" textdraw and you used "TextDrawDestroy" to destroy it,
      it would destroy ID 0 because the value of "td[playerid][1]" is 0. Now by using the "TextDrawDestroyEx" function
      you would be on the safe side because it only destroys textdraws with IDs greater than 0 that means IT HAD to be created
      in order to have an ID greater than 0.
    */

    TextDrawDestroyEx(td[playerid][2]);
    TextDrawDestroyEx(td[playerid][3]);
    return 1;
}

//I purposely didn't use a loop for the last three, just to let others see what we're dealing with.
What am saying is that I think SA-MP could consider this for a future update, maybe not change the function, but upgrade the "TextDrawDestroy" function. I started using this method and tested it and there has been no bugs/glitches with textdraws and IDs getting mixed up.
Please leave your comments/ideas, thanks.
Reply
#2

Here's what I know:
  • The first textdraw created is '0'
  • If you attempt to use TextDrawDestroy on a non-existing textdraw, it will destroy textdraw '0'
  • A GMX will destroy all textdraws except for '0'
The method I use to keep this issue from happening is this

pawn Code:
public OnGameModeInit()
{
    while(_:TextDrawCreate(0,0," ")==0)continue;
    return 1;
}
This will bypass textdraw '0'
Unfortunately, it also creates textdraw '1', meaning you lose access to 2 textdraws. Also it will not support being ran more than once (so only on OnGameModeInit, not OnFilterScriptInit).

The only alternative you can use so that you can keep textdraw '0' and '1', is to keep an array of associated boolean variables to determine if the textdraw has been created AND to destroy ALL textdraws under OnGameModeExit.

pawn Code:
new bool:TextDrawIsActive[MAX_TEXT_DRAWS];
stock Text:TextDrawCreate2(Float:x, Float:y, text[])
{
    new Text:tdID=TextDrawCreate(x,y,text);
    TextDrawIsActive[_:tdID]=true;
    return Text:tdID;
}
stock TextDrawDestroy2(Text:textdraw)
{
    if(TextDrawIsActive[_:textdraw])
    {
        TextDrawIsActive[_:textdraw]=false;
        TextDrawDestroy(textdraw);
        return true;
    }
    return false;
}

public OnGameModeExit()
{
    for(new td; td<MAX_TEXT_DRAWS; td++)TextDrawDestroy2(Text:td);
}

EDIT:
On further examination of your code, your TextDrawDestroyEx doesn't take into consideration that the end-user is trying to destroy a textdraw ID that hasn't been created yet (other than '0')
If I were to create TextDraw '5', for example, then try to destroy it twice, it will destroy textdraw '5' then '0'.

The reason I go with just creating TextDraws '0' and '1' at OnGameModeInit, is so I don't use up unnecessary RAM. However; on second thought, I think that 2 textdraws probably uses up a considerable amount of RAM as well (considerable as compared to an array)
Reply
#3

If the textdraw ID is more than 0 that means it HAS to be created therefore it does take into consideration that textdraws that haven't been created, can't be deleted.
Reply
#4

Quote:
Originally Posted by Y_Less
View Post
No, what you're saying is that SA-MP should write an intelligent server than can guess when you've made a mistake in your code and correct it for you automatically. As far as the server is concerned you created one text draw then destroyed it - it is doing EXACTLY what you told it to and there are NO bugs here! A better idea is to properly test your code and remove any bugs, especially as there is no guarantee that ANY TD will be TD zero if say a filterscript has already made one, so you have no way of knowing in advance whether to use your "Ex" function or not.

Here is a better idea: Don't initialise all your variables to a valid Text Draw ID, set them to -1 instead!
Or... SAMP team just makes it so ID 0 isn't deleted when it's not the intended target, whether either (ID '0' or the intended target) exists or not.



Quote:
Originally Posted by Tee
View Post
If the textdraw ID is more than 0 that means it HAS to be created therefore it does take into consideration that textdraws that haven't been created, can't be deleted.
Textdraw variables aren't set to 0 when a textdraw is destroyed.

pawn Code:
new Text:text=TextDrawCreate(0,0," "); //ID 5, for example's sake
TextDrawDestroy(text); //ID 5 Destroyed
printf("%d",_:text); //Still prints 5, so...
TextDrawDestroy(text); //attempts to destroy ID 5 again
Reply
#5

Quote:
Originally Posted by Y_Less
View Post
Here is a better idea: Don't initialise all your variables to a valid Text Draw ID, set them to -1 instead!
I also considered that.
Reply
#6

Quote:
Originally Posted by Y_Less
View Post
And how can they POSSIBLY know when it is or is not the intended target? This is what I'm saying about an intelligent SA-MP server, only in that case it would have to be psychic to know which one you really want to destroy or not.
Are you telling me that the server does NOT keep any textdraw information in memory? That it doesn't know what it's doing when it sends information for TextDrawShow to players. So when I create a textdraw that every single player receives the information then the server just dumps it?

All the server has to do is keep the textdraw information in a list, associating each one with an ID (which I'm pretty sure it does, but then again, I didn't make it) so when TextDrawDestroy calls for a textdraw inwhich the ID is NOT on the list, it should just return, instead of deciding that if the ID doesn't exist on the list, to just delete ID 0.



Or you could just use my work around, which is tested
Reply
#7

Quote:
Originally Posted by Y_Less
View Post
Why does it?

pawn Code:
TextDrawDestroy(Text:42);
Who knows if that has been created or not? Anyway, as I said, just don't initialise all your variables to zero because it's a valid TD ID, then you.
The server does. https://sampwiki.blast.hk/wiki/TextDrawCreate
Reply
#8

Sorry but this thread is retarded. How is the server supposed to know whether you're intending to delete textdraw ID 0 intentionally or not?

If you go back to some of the original topics that were created during the SA-MP 0.2 pre-release scripting builds (i.e. when textdraws were first introduced) they explain how to create/destroy and properly initialize variables for textdraws.

Code:
new Text:PlayerSpeedoDisplay[MAX_PLAYERS] = {Text:INVALID_TEXT_DRAW, ...};

initSpeedo(playerid)
{
   // The texdraw already exists for this playerid index
    if(PlayerSpeedoDisplay[playerid] != Text:INVALID_TEXT_DRAW)
    {
        return;
    }

    PlayerSpeedoDisplay[playerid] = TextDrawCreate(...);
}

destroySpeedo(playerid)
{
    // No textdraw exists for this playerid index
    if(PlayerSpeedoDisplay[playerid] == Text:INVALID_TEXT_DRAW)
    {
        return;
    }
    TextDrawDestroy(PlayerSpeedoDisplay[playerid]);
    PlayerSpeedoDisplay[playerid] = Text:INVALID_TEXT_DRAW;
}
It is common sense when you think about it...
Reply
#9

Quote:
Originally Posted by Joe Staff
Посмотреть сообщение
Are you telling me that the server does NOT keep any textdraw information in memory? That it doesn't know what it's doing when it sends information for TextDrawShow to players. So when I create a textdraw that every single player receives the information then the server just dumps it?

All the server has to do is keep the textdraw information in a list, associating each one with an ID (which I'm pretty sure it does, but then again, I didn't make it) so when TextDrawDestroy calls for a textdraw inwhich the ID is NOT on the list, it should just return, instead of deciding that if the ID doesn't exist on the list, to just delete ID 0.



Or you could just use my work around, which is tested
But that's not what was proposed. Tee was complaining that the "forum" variable had not been initialised, so had a value of 0, so when destroyed it destroyed TD 0! How is the server meant to know that's not what you want to do?
Reply
#10

Quote:
Originally Posted by Y_Less
View Post
But that's not what was proposed. Tee was complaining that the "forum" variable had not been initialised, so had a value of 0, so when destroyed it destroyed TD 0! How is the server meant to know that's not what you want to do?
I suppose I never actually read the OP, lol. I assumed he was talking about the issue with ID 0 being deleted when a non-existing (but previously created) textdraw was deleted.
Reply
#11

This is extremely easy to fix.

Init your textdraw arrays/variables like so:

pawn Code:
new Text:pTextdraw[MAX_PLAY] = {Text:INVALID_TEXT_DRAW, ...};
Hook the TextDrawDestroy function:

pawn Code:
stock safe_TextDrawDestroy(&Text:text)
{
    if(text == Text:INVALID_TEXT_DRAW) return print("-> YOU TRIED TO DELETE A TEXTDRAW THAT DIDN'T EXIST!");
    TextDrawDestroy(text);
    text = Text:INVALID_TEXT_DRAW;
    return 1;
}
#if defined _ALS_TextDrawDestroy
    #undef TextDrawDestroy
#else
    #define _ALS_TextDrawDestroy
#endif
#define TextDrawDestroy safe_TextDrawDestroy
What's all the fuss about?

As you can see the Text:text parameter is changed to a reference (&Text:text) so we can set the variable to INVALID_TEXT_DRAW once it's deleted.
Reply
#12

A problem that's fixable, but still technically a problem. It shouldn't need workarounds
Reply
#13

The problem isn't with SA:MP. The problem is with your code. Computers do what you tell them, they don't care if that's what you intended to do or not and that applies to PAWN scripts also. Yes this could be natively done in SA:MP but meh.
Reply
#14

But its not a problem! The only problem is that you are deleting the wrong TD and somehow this is SA-MP's fault.
Reply
#15

Of course not, but why not consider the worse case scenario so the end user doesn't have to do it for you?

Edit: for themselves, I should say.

Your programming shouldn't include the probability of issues. Just because something is easily averted doesn't make it a non-issue.
Reply
#16

OK, I will suggest to Kalcor that he fixes this problem if you can answer this question:

pawn Код:
DestroyTextDraw(Text:0);
This is what the problem boils down to - you may or may not want to really destroy this TD, the ID could be correct, or it could be a wrongly set variable. If you can provide a fool-proof algorithm to determine whether or not this TD should be destroyed, then please tell me and it can be implemented inside SA-MP!
Reply
#17

I guess IDs could start at 1 like vehicles but tbh why should Kye make a 'fix' for something that isn't a 'problem'?
Reply
#18

I think this topic should be locked because it's just going nowhere...
Reply
#19

People make mistakes so I think either the TD IDs should start from 1 (like vehicles) or every TextDraw you create, you assign it a value of -1.
Reply
#20

I seem to be arguing over the wrong thing.


It was my belief that this:
pawn Код:
TextDrawDestroy(Text:5);
TextDrawDestroy(Text:5);
Would result in Textdraw '0' being destroyed, but through some testing, that does not appear to be the case.
In which case I withdraw my previous statements and I apologize.


However, this could be changed by simply starting Textdraws at ID 1, like previously mentioned.
Reply


Forum Jump:


Users browsing this thread: 3 Guest(s)