PDA

View Full Version : TextDrawDestroy (a workaround/upgrade?)


Tee
10/07/2012, 06:21 PM
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 (http://forum.sa-mp.com/search.php?searchid=4871696)" or "mixed up (http://forum.sa-mp.com/search.php?searchid=4871690)".

Now here is how textdraw IDs work:


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:


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:


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.

Joe Staff
10/07/2012, 06:55 PM
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


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.


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)

Y_Less
10/07/2012, 07:02 PM
What am saying is that I think SA-MP could consider this for a future update

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!

Tee
10/07/2012, 07:05 PM
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.

Joe Staff
10/07/2012, 07:06 PM
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.



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.


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

Tee
10/07/2012, 07:07 PM
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.

Y_Less
10/07/2012, 07:09 PM
Why does it?


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.

Plus, who is to say that instead of initialising your "forum" variable to a TD, you do that, destroy it and create a new unrelated TD that now has the same ID. But if you forgot to reset the "forum" variable it will now point to the new TD instead of nothing. There are just TOO MANY options to be able to write code that can guess and correct any bug that you might come up with. I'm sorry to say that bugs are a part of coding and you have to find and fix them.

And no "the SA-MP server is not intelligent" is not a bug!

Edit: Some of that was posted before I saw other replies:

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.

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.

Joe Staff
10/07/2012, 07:17 PM
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

Tee
10/07/2012, 07:21 PM
Why does it?


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. http://wiki.sa-mp.com/wiki/TextDrawCreate

Jay_
10/07/2012, 07:36 PM
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.


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...

Y_Less
10/07/2012, 07:52 PM
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?

Joe Staff
10/07/2012, 08:03 PM
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.

MP2
10/07/2012, 08:25 PM
This is extremely easy to fix.

Init your textdraw arrays/variables like so:


new Text:pTextdraw[MAX_PLAY] = {Text:INVALID_TEXT_DRAW, ...};


Hook the TextDrawDestroy function:


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.

Joe Staff
10/07/2012, 08:47 PM
A problem that's fixable, but still technically a problem. It shouldn't need workarounds

MP2
10/07/2012, 08:49 PM
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.

Y_Less
10/07/2012, 08:50 PM
But its not a problem! The only problem is that you are deleting the wrong TD and somehow this is SA-MP's fault.

Joe Staff
10/07/2012, 09:22 PM
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.

Y_Less
10/07/2012, 09:39 PM
OK, I will suggest to Kalcor that he fixes this problem if you can answer this question:


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!

MP2
10/07/2012, 09:53 PM
I guess IDs could start at 1 like vehicles but tbh why should Kye make a 'fix' for something that isn't a 'problem'?

Jay_
10/07/2012, 10:36 PM
I think this topic should be locked because it's just going nowhere...

Tee
10/07/2012, 10:57 PM
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.

Joe Staff
10/07/2012, 11:10 PM
I seem to be arguing over the wrong thing.


It was my belief that this:

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.

Kar
11/07/2012, 12:47 AM
I do 2 things to avoid this issue

1. Create a textdraw wrapper.. aka a textdraw with id 0 for player texts and server textdraws, so incase anything happens, it will try to destroy id 0. (which is the invisible unused textdraw)

2. Initilize textdraws as INVALID_TEXT_DRAW (INVALID_PLAYER_TEXT_DRAW)

3. On destroy, set the textdraw variable to INVALID_TEXT_DRAW (INVALID_PLAYER_TEXT_DRAW)

works perfect.

Roko_foko
11/07/2012, 07:01 AM
new Text:pTextdraw[MAX_PLAY] = {Text:INVALID_TEXT_DRAW, ...};

is for one dimension.

How to declare two dimension textdraws?
for example

new PlayetText: Options[MAX_PLAYERS][MAX_OPTIONS]=

Joe Staff
11/07/2012, 07:37 AM
new Text:pTextdraw[MAX_PLAY] = {Text:INVALID_TEXT_DRAW, ...};

is for one dimension.

How to declare two dimension textdraws?
for example

new PlayetText: Options[MAX_PLAYERS][MAX_OPTIONS]=

I believe it's something like this


new Array[ARRAY_SIZE][ARRAY_SIZE]= { {number, ...}, ...};

MP2
11/07/2012, 07:39 AM
No idea. By the way I thought of a problem with my hooking method. If you destroy textdraws in a loop:


for(new i=0; i<MAX_TEXTDRAWS; i++)
{
TextDrawDestroy(i);
}

It's going to set 'i' to INVALID_TEXT_DRAW. Nobody really deletes all textdraws in a loop though so idk. Not sure what would happen if you put a literal integer in like

TextDrawDestroy(69);

Both things that should never be done though.

Slice
11/07/2012, 08:36 AM
What I've seen some people do is improperly initialize arrays of TextDraw IDs, such as this:
new
Text:g_SomeTextDraw[MAX_PLAYERS] = {Text:INVALID_TEXT_DRAW}
;

The proper way to do it is this:
new
Text:g_SomeTextDraw[MAX_PLAYERS] = {Text:INVALID_TEXT_DRAW, ...} // <- dots
;

If you leave the dots out, only the first element will be INVALID_TEXT_DRAW; the rest will be 0.

Edit: Oops, didn't see page 2.


I believe it's something like this


new Array[ARRAY_SIZE][ARRAY_SIZE]= { {number, ...}, ...};


The only way to initialize multi-dimensional arrays is by looping, preferably in On(GameMode/FilterScript)Init.

Edit 3: Like this (http://forum.sa-mp.com/showthread.php?p=1979135#post1979135).

MP2
11/07/2012, 09:10 AM
To prevent having to init the var/arrays it'd probably be easier just to skip ID 0 (create a textdraw that is never shown and never deleted). Then in a hooked function if you try to delete ID 0 just deny it. That'd save initializing variables.

Y_Less
11/07/2012, 11:16 AM
Why is that better? You lose a text draw slot and have to run custom code in the destruction function, instead of using an in-built compiler feature that's takes half a second to use.

MP2
11/07/2012, 12:18 PM
What in-built compiler feature? Initializing variables to INVALID_TEXT_DRAW? It's easier to skip that part. Plus:

http://forum.sa-mp.com/showpost.php?p=1979461&postcount=3341

Kar
12/07/2012, 04:40 AM
What in-built compiler feature? Initializing variables to INVALID_TEXT_DRAW? It's easier to skip that part. Plus:

http://forum.sa-mp.com/showpost.php?p=1979461&postcount=3341

hahaha funny.

http://forum.sa-mp.com/showpost.php?p=1979135&postcount=3336


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

MP2
12/07/2012, 09:14 AM
Yes but if you forget to do that accidentally then you're fucked - by just 'reserving' ID 0 you don't have to worry about initializing variables.

Y_Less
12/07/2012, 11:59 AM
You can't possibly write code to account for anything any scripter may forget to do. What if they forget to reset a variable after destroying a TD? What if they forget to assign a TD to a variable in the first place? What if they forget to use "INVALID_TEXT_DRAW" and instead use "-1", then when the invalid ID gets changed to 0 breaks all their code?

If you forget to initialise a variable when you declare it (not exactly hard, and should be an automatic part of declaring variables), you can't blame other people for the resulting bugs!

Kar
12/07/2012, 12:00 PM
So what if they forget it? If I see theres a textdraw mixing up problem I usually look at the ones that are mixed up and the first ones created then I assess the problem from there..

It's not like you can't go back and fix it ;\

Y_Less
12/07/2012, 12:24 PM
While we're at it, why not get rid of possibly the biggest source of bugs ever - playerid 0?

MP2
12/07/2012, 12:29 PM
It's only an idea. It's not to prevent bugs, it's to save time (in the long-run).

Kar
12/07/2012, 12:31 PM
While we're at it, why not get rid of possibly the biggest source of bugs ever - playerid 0?

Ah I didn't think this was a global problem. playerid 0 .. yes I noticed that player can have bugs sometimes, but most of the time its your script. That player usually bugs due to buffer overflows / not resetting arrays and all kinds of shit, but if you play your game right, playerid 0 won't be a problem. I fixed mine in my cnr some months ago. No problems anymore

Slice
12/07/2012, 12:32 PM
Here's a little include I wrote to initialize multi-dimensional arrays. So far it's very simple, but I might add some more features and officially release it.


// Global scope (not inside functions):

new
// last dimension will be INVALID_TEXT_DRAW, ...
Array:Text:g_Dim2[5][10] = {INVALID_TEXT_DRAW},

// last dimension will be: 1, 2, 3, 4, 1, 2, 3, 4, 1, 2
Array:g_Dim3[5][5][10] = {1, 2, 3, 4}
;

Also works for initializing enums, though you'd have to remove tags (_:5.0 for floats, for example).

Download: http://pastebin.com/sZFWwBzB (requires YSI)

MP2
12/07/2012, 12:43 PM
It's not ME that has a problem with initializing variables/arrays to INVALID_TEXT_DRAW, I'm talking about the less experienced players that don't really even know what an array is. If ID 0 was 'reserved' it would make their life easier.

Slice
12/07/2012, 12:46 PM
No offense, but if you keep thinking like that, you won't come very far as a programmer.

Edit: Above post was edited.

MP2
12/07/2012, 12:50 PM
Yeah I know, I'm not even sure if I want to be a programmer anymore. I have a lot to learn.

(I edited my post, original post was something like 'I like to script things simply not complicated').

Anyway - off-topic.

Y_Less
12/07/2012, 01:10 PM
Here's a little include I wrote to initialize multi-dimensional arrays. So far it's very simple, but I might add some more features and officially release it.


I was wondering if you would do that (I was tempted after I saw your Useful Functions post). Nicely done (though I'm not sure how the mutli-line declaration you've used in that example will work, but I've not looked closely).

Slice
12/07/2012, 01:16 PM
I settled for prepending "stock @_Ar<ARRAY NAME>" so the list either carries on or stops by a semi-colon.

Y_Less
12/07/2012, 01:19 PM
Ahh. Something I have tried to solve numerous times (well, one of the things I'm constantly mulling over in the back of my mind) is if it's possible to detect HOW the variables were declared, so instead of "stock" you could put "new" or "static const" there based on what was first typed.

Patrik356b
22/07/2012, 02:13 PM
As i see it, this does explain an issue based on the following circumstances:

PlayerX is playing...
Server restarts...
PlayerX crashes...
PlayerX connects...


When that happens a GPS textdraw i made looks a bit weird, but the script fixes that when the player spawns.

This is because the textdraw string for that player does not initialize correctly, Even tho i have a proper use of TextDrawDestroy in OnGameModeExit and OnPlayerDisconnect.

Instead of showing "] ] ]". It actually shows nothing, so either the textdraw string is initalized as null or a string with characters that does not show up in a textdraw. E.g: space, underscore, etc.

[HLF]Southclaw
22/07/2012, 08:25 PM
I had this problem, in my gamemode I simply set all text-draws and player text-draws to be created on OnGameModeInit and destroyed on OnGameModeExit, this works well enough. It's best to just avoid creating/deleting them in runtime in my opinion.


The only problem I have is if I do this:

Start server
Load a filterscript with textdraws
Restart the server

Then I get problems with gamemode textdraws taking instructions from filterscripts.

But then again I only load and de-load filterscripts for testing purposes, when my gamemode is hosted the only filterscript won't have any GUI, all the main stuff is in the gamemode.