• tmysql4 - A multi-connection version of tmysql3 (Now with mysqloo wrapper!)
    384 replies, posted
I think I found a bug in the tmysql code. Running [LUA] local tmysqlDB = tmysql.init(...) tmysqlDB:Query("SELECT 1", function(result) causerror() end) for i = 1, 10 do tmysqlDB:Query("SELECT 1", function(result) print("result") end) end [/LUA] will cause tmysql to potentially not run some of the callbacks as well as leak memory. I am pretty sure it's caused by [URL="https://github.com/bkacjios/gm_tmysql4/blob/master/src/gm_tmysql.cpp#L437"]this line[/URL] in the sourcecode. [QUOTE=Unknown Gamer;51598160] [CODE][ERROR] Error in my_thread_global_end(): 2 threads didn't exist.[/CODE] [/QUOTE] I am pretty sure that just means tmysql didn't wait for all threads to finish before the map change happened. Edit: I checked and it appears to be because tmysql doesn't call mysql_thread_end in all threads that use mysql. I doubt it would cause any crashes though.
Can you post a reproducible code that I can use to test this? I have a copy I can compile on windows. [editline]30th December 2016[/editline] [QUOTE=syl0r;51602014][LUA] local tmysqlDB = tmysql.init(...) tmysqlDB:Query("SELECT 1", function(result) causerror() end) for i = 1, 10 do tmysqlDB:Query("SELECT 1", function(result) print("result") end) end [/LUA][/QUOTE] This on Windows causes it to only run query 1 and the leftover 9 will be ignored. This: [LUA] local tmysqlDB = tmysql.init(...) for i = 1, 10 do tmysqlDB:Query("SELECT 1", function(result) print("result") end) end [/LUA] Will run all 10 just fine. I will look into this. [editline]30th December 2016[/editline] Alright I have it running locally without the callbacks skipping. [CODE]lua/autorun/server/asd.lua:5: attempt to call global 'causerror' (a nil value) result 1 result 2 result 3 result 4 result 5 result 7 result 6 result 8 result 9 result 10[/CODE] It seems that ThrowError causes things to go wrong.
I accepted your pull request and compiled everything.
I really think that you should set NUM_THREADS_DEFAULT to 1 instead of 2. It can (and probably does) cause so many unexpected results without providing much of a benefit. If someone really wants to use two connections I don't think there's anything stopping them from using two different database instances. (with tmysql3 that obviously wasn't an option) Or maybe just let users specify the amount of threads when creating the database. Just to give a quick example and dig up some really old post from this thread (when I already mentioned this issue). [QUOTE=FPtje;46765333]Concurrency issues? Unexpected behaviour? Are you talking about your own inability to handle it or are you saying the module is buggy? [Some more stupid shit][/QUOTE] Even DarkRP has race conditions when using tmysql even though FPtje clearly knows what he is doing. If he can't get it right how do you expect the average scriptfodder coder to get it right. [LUA] -- Assuming player has $0 to start with ply:addMoney(1000) ply:addMoney(1000) [/LUA] After calling these functions the money the player has in the database can not be guaranteed to be $2000. In fact when running the db on the same machine there is a 33% chance of it giving you the wrong amount of money. I don't even want to imagine how much could go wrong if this were to be tested with the PLAYER:payMoney() function and two players that just keep giving each other money. Of course this is just a synthetic example and most of these errors will correct themselves in the DarkRP example, but think about this: You can never run two queries one after another if they depend on each other and guarantee correctness at the same time. In pretty much all gamemodes there are plenty of examples where the order matters (money is one of them, items too). Also you should note that this is not just about possible money bugs but it can also cause errors in queries that really should never fail, which can produce further unexpected behavior. While it is possible to fix these issues by introducing a manual query queue that waits for each query to finish before it starts the next one, it'd require you to wait at least one tick each time before the next query can be started. This means that in those cases having two connections is actually going to be much slower than having one (if you want to do it correctly).
tmysql4 should ensure query execution order. Not doing so is a [B]very[/B] serious bug. One cannot ever hope to reason about the data held in the database when two or more queries are run that modify the same data. If tmysql4 cannot ensure query execution order, having one thread should not only be the default, it should be impossible to set more than one. After all, a config option to enable race conditions is extremely stupid. I thought the threaded stuff was about receiving the results. My last post talks about the order in which data is [U]received[/U]. That order can be arbitrary for all I care.
[QUOTE=FPtje;51614521]tmysql4 should ensure query execution order. Not doing so is a [B]very[/B] serious bug. One cannot ever hope to reason about the data held in the database when two or more queries are run that modify the same data. If tmysql4 cannot ensure query execution order, having one thread should not only be the default, it should be impossible to set more than one. After all, a config option to enable race conditions is extremely stupid. I thought the threaded stuff was about receiving the results. My last post talks about the order in which data is [U]received[/U]. That order can be arbitrary for all I care.[/QUOTE] I don't even see any reason to use tmysql anymore. mysqloo seems to be bugfree unlike this, has a fixed execution order and stuff like prepared statements, which makes sqli way harder. I switched one of my addons from tmysql to mysqloo few days a go because of that. I think it's time for tmysql to give up and surrender or evolve
I really don't give a shit about gmod or this module any more. Consider this my last post on FP. Sooooo, yeah you should probably update and get off the module if there's a better solution. The only reason I even updated it was because someone made a pull request and had the tools setup to compile it.
Soo... switch to mysqloo v9?
[QUOTE=Unknown Gamer;51617302]Soo... switch to mysqloo v9?[/QUOTE] Or fix it yourself if that bug is a deal-breaker for you.
[QUOTE=code_gs;51618519]Or fix it yourself if that bug is a deal-breaker for you.[/QUOTE] Not real a deal breaker and I know [B][I]Nothing[/I][/B] about C++ and making a module. I'm just talking about support wise and how much tmysql4 can offer compared to the new mysqloo v9
[QUOTE=FPtje;51614521]tmysql4 should ensure query execution order. Not doing so is a [B]very[/B] serious bug. One cannot ever hope to reason about the data held in the database when two or more queries are run that modify the same data.[/QUOTE] It isn't a bug, it's how the module is designed. But it should be made clear, that using a NUM_THREADS_DEFAULT over 1 doesn't provide consistency when used incorrectly. Also it's not a bug, as there are many (smart) ways to make sure this doesn't happen. Let's take the example with add money, there can be two cases how it is scripted: [code] -- case 1: not very thread friendly function addMoney(player, amount) db:query("Update ....") player:money = player:money + amount end -- case 2: perfectly friendly with multiple mysql threads function addMoney(player, amount) player:money = player:money + amount end function threeMinutesSaveTimer() -- iterate through all players, and call save() which also saves the money end [/code] I prefer case 2, as it makes sure that everything is consistent in the database. Disadvantage is that you may loose a bit of progress when the server crashes, but the loss is minimal. As far as I know thats also the way bigger mmo servers handle player states. (example of inconsistent database when using case 1: Two players trade, the items of one player get saved after trade, then the server crashes, the second player now has too much items or lost items after the server started up again). I would suggest defaulting the number of threads to 1, but there should be an easy way to increase it.
[QUOTE=Sapd;51632748]It isn't a bug, it's how the module is designed. But it should be made clear, that using a NUM_THREADS_DEFAULT over 1 doesn't provide consistency when used incorrectly. Also it's not a bug, as there are many (smart) ways to make sure this doesn't happen. [/QUOTE] While it may be designed that way I very much doubt any attention was paid to the consequences of this design choice. None of these "smart" ways should be acceptable. [QUOTE=Sapd;51632748] I prefer case 2, as it makes sure that everything is consistent in the database. Disadvantage is that you may loose a bit of progress when the server crashes, but the loss is minimal. As far as I know thats also the way bigger mmo servers handle player states. (example of inconsistent database when using case 1: Two players trade, the items of one player get saved after trade, then the server crashes, the second player now has too much items or lost items after the server started up again). [/QUOTE] Case 2 makes sure that the database is almost never consistent with the game. You already demonstrated yourself why that is a very bad idea. Additionally imagine having multiple servers. You literally create massive race conditions using a timer like that. Maybe some bigger MMOs handle it that way but they're just doing it wrong then. Furthermore the money example isn't the only thing that can go wrong. Any two queries that require to be executed in order will have these issues. How are you going to design a "smart" way that works for items without saving the entire inventory every time?
[QUOTE=Sapd;51632748]It isn't a bug, it's how the module is designed. But it should be made clear, that using a NUM_THREADS_DEFAULT over 1 doesn't provide consistency when used incorrectly. Also it's not a bug, as there are many (smart) ways to make sure this doesn't happen. Let's take the example with add money, there can be two cases how it is scripted: [code] -- case 1: not very thread friendly function addMoney(player, amount) db:query("Update ....") player:money = player:money + amount end -- case 2: perfectly friendly with multiple mysql threads function addMoney(player, amount) player:money = player:money + amount end function threeMinutesSaveTimer() -- iterate through all players, and call save() which also saves the money end [/code] I prefer case 2, as it makes sure that everything is consistent in the database. Disadvantage is that you may loose a bit of progress when the server crashes, but the loss is minimal. As far as I know thats also the way bigger mmo servers handle player states. (example of inconsistent database when using case 1: Two players trade, the items of one player get saved after trade, then the server crashes, the second player now has too much items or lost items after the server started up again). I would suggest defaulting the number of threads to 1, but there should be an easy way to increase it.[/QUOTE] Can you [b]prove[/b] that the query after six minutes won't execute before the one after three minutes? It's a ridiculous hypothetical situation, but the point is that increasing the time between two queries that modify the same data isn't a [u]proper[/u] solution. It's a practical workaround.
[QUOTE=FPtje;51635530]Can you [b]prove[/b] that the query after six minutes won't execute before the one after three minutes? It's a ridiculous hypothetical situation, but the point is that increasing the time between two queries that modify the same data isn't a [u]proper[/u] solution. It's a practical workaround.[/QUOTE] Dropped some breadcrumbs on my phone and started freaking out because of you profile image on the front page as most recent post in this subforum. Made me think that the crumbs were spinning
[QUOTE=syl0r;51634506] Case 2 makes sure that the database is almost never consistent with the game. You already demonstrated yourself why that is a very bad idea. Additionally imagine having multiple servers. You literally create massive race conditions using a timer like that. Maybe some bigger MMOs handle it that way but they're just doing it wrong then. Furthermore the money example isn't the only thing that can go wrong. Any two queries that require to be executed in order will have these issues. How are you going to design a "smart" way that works for items without saving the entire inventory every time?[/QUOTE] Can you explain further why I create race conditions? Having a timer saving like every three minutes don't create any inconsistency. As the database has always a sense-full state. However if you try to save every action the player did instantly, you always have small times of inconsistent states in the database as your actions are not atomically for sure. When saving the whole states of the player in one rush, then there are no queries which need to be executed in order, as you simply save the states as such. That also works with inventory systems: [code] -- pseudo code function moveInventoryItem(player, slotTo, slotFrom, item) -- do something to move items slotTo.dirty = true slotFrom.dirty = true end function saveInventory(player) for (slot in slots) if (slot.dirty == true) -- do query to update slot slot.dirty = false end end end [/code] This has also the advantage of using less queries. When a player moves his items in his (for example grid like) inventory around, he doesn't create a query for every move. Instead modified slots get flagged, and then updated. Why save his inventory while he is moving things around, instead of saving queries and saving it afterwards? Also, you avoid programming mistakes as you don't need to add "save" code on every place where something changes. And regarding your example a player connecting to multiple servers: Case 2 is better, as the state of the player playing on one server gets overrides with the other state, so it's definitely harder to dupe items by this method, as when using the case 1 method Also I want to refer to you saying "[...]handle it that way but they're just doing it wrong then", there is no wrong or right when talking about programming patterns. Every pattern has it's own disadvantage and advantages a good developer should know them and then decide for himself which pattern to choose. I also want to make clear, that I also think that case 1 is suited pattern. [QUOTE=FPtje;51635530]Can you [b]prove[/b] that the query after six minutes won't execute before the one after three minutes? It's a ridiculous hypothetical situation, but the point is that increasing the time between two queries that modify the same data isn't a [u]proper[/u] solution. It's a practical workaround.[/QUOTE] This is definitely something to think about. However this shouldn't happen, and if it happens than there is something seriously wrong and then you have other problems to think about first. I also want to emphasise that the difference between the two methods is NOT a simple increase in time between two queries. It is not a workaround, it is a completely different programming pattern. In game programming, some call it dirty flag pattern.
[QUOTE=Sapd;51635986]Can you explain further why I create race conditions? Having a timer saving like every three minutes don't create any inconsistency. As the database has always a sense-full state.[/QUOTE] The race condition arises when you have more than one server. It would be extremely easy to exploit two servers that are linked together and use a 3 minute timer to save the data. And don't try and fix this by saving the player's inventory when the player leaves, then you have yet another race condition as to whether or not the 3 minute save timer finishes before the leave query. This means that even rejoining the [B]same [/B]server can cause such race conditions. [QUOTE=Sapd;51635986] And regarding your example a player connecting to multiple servers: Case 2 is better, as the state of the player playing on one server gets overrides with the other state, so it's definitely harder to dupe items by this method, as when using the case 1 method [/QUOTE] Player joins server A, drops all his money. Player joins server B. It still gives him the money he had before dropping it. Player drops $1 on server B and waits for the save timer to go through. Player joins back on server A and the player has all his money back even though he dropped it all. Theoretically the same could arise when instantly updating the database and the query takes 3 minutes to go through but there are ways to fix that. [QUOTE=Sapd;51635986] However if you try to save every action the player did instantly, you always have small times of inconsistent states in the database as your actions are not atomically for sure. [/QUOTE] If you do it right there would not be a single state in the database that doesn't reflect some consistent state in the game. [QUOTE=Sapd;51635986] When saving the whole states of the player in one rush, then there are no queries which need to be executed in order, as you simply save the states as such. That also works with inventory systems: This has also the advantage of using less queries. [...] [/QUOTE] You are right that is possible, I would still argue the negative aspects greatly outnumber the benefits though. Less queries aren't always a good thing. Database systems are designed to handle thousands of queries per second so why not make use of that?
[QUOTE=syl0r;51636067]The race condition arises when you have more than one server. It would be extremely easy to exploit two servers that are linked together and use a 3 minute timer to save the data. And don't try and fix this by saving the player's inventory when the player leaves, then you have yet another race condition as to whether or not the 3 minute save timer finishes before the leave query. [...] Player joins server A, drops all his money. Player joins server B. It still gives him the money he had before dropping it. Player drops $1 on server B and waits for the save timer to go through. Player joins back on server A and the player has all his money back even though he dropped it all. Theoretically the same could arise when instantly updating the database and the query takes 3 minutes to go through but there are ways to fix that. [/QUOTE] Yes, I don't say it solves the problem, it makes it only a bit harder for the player to exploit. Synchronising two servers (with same player states) is a different matter, case 1 and case 2 don't provide a solution to this problem. The amount of threads also don't matter when considering this problem, as there will be always this problem when a player is permitted two join two worlds simultaneously. [QUOTE=syl0r;51636067] This means that even rejoining the same server can cause such race conditions. [/QUOTE] Yes, but only when the pattern is not implemented strictly. When implementing strictly, you don't save a player when leaving, instead you keep his state even when he is disconnected. When he rejoins before save, the state (the player object) can be used further, when a save happens he is saved (like all players of course) and his object can be thrown away. This resolves the race condition, but is of course a bit of effort. (In other games this is also used as it provides the benefit, that the player stays ingame for a short amount while he is disconnected. So he can't simply leave PvP fights. But sadly it's not so easy with the source engine.) [QUOTE=syl0r;51636067] If you do it right there would not be a single state in the database that doesn't reflect some consistent state in the game. [/QUOTE] But doing this right, is also effort. You need to be very fussy about every query you write, every query and every transaction needs to be checked if it doesn't break consistency. Much harder for a unexperienced programmer in my opinion. [QUOTE=syl0r;51636067] You are right that is possible, I would still argue the negative aspects greatly outnumber the benefits though. Less queries aren't always a good thing. Database systems are designed to handle thousands of queries per second so why not make use of that?[/QUOTE] That's why I say every developer has to decide for himself which is the way to go. Both are right, and it's possible to discuss about which is better nearly forever. Often developers use the way they always used (which is perfectly okay).
[QUOTE=Sapd;51635986] [QUOTE=FPtje;51635530]Can you [b]prove[/b] that the query after six minutes won't execute before the one after three minutes? It's a ridiculous hypothetical situation, but the point is that increasing the time between two queries that modify the same data isn't a [u]proper[/u] solution. It's a practical workaround.[/QUOTE] This is definitely something to think about. However this shouldn't happen, and if it happens than there is something seriously wrong and then you have other problems to think about first. I also want to emphasise that the difference between the two methods is NOT a simple increase in time between two queries. It is not a workaround, it is a completely different programming pattern. In game programming, some call it dirty flag pattern.[/QUOTE] The decision to update every three minutes rather than immediately is not one to be taken as lightly as you are with this shit. It's one of the biggest data-related trade-offs you can take in a game. MMORPG games do this because they've got servers with hundreds to thousands of players. Running a query every time someone bashes their knob against a rock is infeasible. They [i]have[/i] to. Collecting updates and sending them every three minutes isn't something you decide to do because the author of the god damn [i]library[/i] was too incompetent to imagine that query execution order might just be an essential property. Also, you completely failed to understand the essence of my three minute argument. Of [b]COURSE[/b] something's terribly wrong when that happens. My point is that concurrent computing is [url=https://en.wikipedia.org/wiki/Nondeterministic_algorithm][b]non-deterministic[/b][/url]. It means that when you have two threads doing two things, you [b]cannot[/b] reason about which thread will be doing its thing first, even if you wait until the god damn sun dies out. From a computer scientific standpoint, you [b]cannot[/b] definitively prove that your dirty ass flag pattern [i]cannot[/i] suffer from the precise fucking problem you so think it's a nice solution for. Without this bug and with the number of threads set to 1, this proof would have been [u]trivial[/u]. It's trivial even to prove that your workaround actually suffers from this issue: Requirements: - TMySQL4 with 2 threads - A big ass table - A big ass arbitrary query At time = 0, execute a big ass query on the big ass table that lasts 3:30 minutes. This query is run on thread #1. This is not unthinkable in MySQL. At time = 9.9 seconds, some irrelevant query is run on thread #2. It lasts 0.3 seconds. At time = 10 seconds, execute the first "dirty flag update" query, this happens to also be scheduled on thread #1, because #2 is also busy. At time = 3:10 minutes, execute the second "dirty flag update" query. This one is executed on thread #2 because it's not doing anything. At time = 3:30 minutes, that big ass query is finally done and the first "dirty flag update" is finally run. It overrides the second one. Your solution: - Doesn't solve the problem. It just [i]hopes[/i] to make it significantly less likely. - Suffers immense problems of itself (easy exploits with multiple servers as Sylor said, loss of a [u]lot[/u] of data when the server goes down, etc.) It's [b]SHIT[/b]. There's a really good way to solve this issue while [I]still[/I] having concurrent queries. That solution is to let programmers have control over which threads get to run their queries. One thread for updates and one (or more) for queries for example would be [U]subliminal[/U] for both data safety and concurrent queries. Maybe even multiple threads for updates with some mechanism that makes sure the different threads deal with different data. Easy enough to prove data consistency there.
[QUOTE=FPtje;51636186] Collecting updates and sending them every three minutes isn't something you decide to do because the author of the god damn [i]library[/i] was too incompetent to imagine that query execution order might just be an essential property. [/QUOTE] You also seem to fail to understand me. Still I DON'T recommend doing it because of the way tmysql4 works, I say it behaves differently when using this way. [QUOTE=FPtje;51636186] Also, you completely failed to understand the essence of my three minute argument. Of [b]COURSE[/b] something's terribly wrong when that happens. My point is that concurrent computing is [url=https://en.wikipedia.org/wiki/Nondeterministic_algorithm][b]non-deterministic[/b][/url]. It means that when you have two threads doing two things, you [b]cannot[/b] reason about which thread will be doing its thing first, even if you wait until the god damn sun dies out. From a computer scientific standpoint, you [b]cannot[/b] definitively prove that your dirty ass flag pattern [i]cannot[/i] suffer from the precise fucking problem you so think it's a nice solution for. Without this bug and with the number of threads set to 1, this proof would have been [u]trivial[/u]. [/QUOTE] Of course I can't prove, as you can't prove that using your patterns will prevent problems with consistency. Like I said it has advantages and disadvantages. [QUOTE=FPtje;51636186] [Prove of issues] [/QUOTE] You exactly know how it's unlikely (yeah keep your wikipedia link to nondeterministic and show it to your fellow students) and I guess you also know how to fix this problem. A simple boolean flag which states if the last save finished. Using your language, I would link now to the "Deterministic" and "Mutex" wikipedia article. Even in the unlikely case AND with forgetting the (mutex-like)variable. I am also sure that you considered that this problem fixes itself? As soon as the next (third) save state gets written, everything will be normal again. My solution (like I said before) was never intended to solve the multiple servers problem, this is a completely different issue. [QUOTE=FPtje;51636186] It's SHIT. [/QUOTE] In your post, I counted 6 asses, but as they are no real female asses, it won't support your argumentation. I would recommend you to get used to a more respectful tone if you and your opinions should be taken seriously outside the Facepunch world and if you want to keep the conversation going. Also I need to excuse that I still fail to see a real argument against my solution provided by you. You don't need to keep trying explaining it to me, as in your point of view I am too incompetent to understand your higher language. However Syl0r provided (in a friendly way) some valid arguments in which I agree. I would suggest ending the conversation here, you keep your point, I keep mine, we don't need to hijack this thread. Despite the problems we talked about, there is still another problem with this library which was not mentioned, this also brings us back to the main topic. The lack of support of transactions always makes things "non-deterministic" and more or less inconsistent, especially when using multiple threads. There also needs a way to get a specific connection to use transactions. Like: [quote] connection = Database:getConnectionFromPool() -- returns a random (or better a unused) connection from the thread pool connection.startTransaction() -- should reserve now the connection until the transaction is finished, should also wait until all queries from this connection finished connection.Query(...) connection.Query(...) -- a lot of more queries for the transaction connection.commit() [/quote] Such a mechanism would provide a real solution for multiple problems.
[QUOTE=Sapd;51636276]Also I need to excuse that I still fail to see a real argument against my solution provided by you.[/QUOTE] Good luck failing to see this: - It doesn't solve the problem in the first place. - It makes it very easy to exploit when multiple servers are connected by giving users three minutes between saves. - On server crash you lose up to three minutes of data. [QUOTE=Sapd;51636276] You exactly know how it's unlikely (yeah keep your wikipedia link to nondeterministic and show it to your fellow students) and I guess you also know how to fix this problem. A simple boolean flag which states if the last save finished. Using your language, I would link now to the "Deterministic" and "Mutex" wikipedia article.[/QUOTE] I can't send wikipedia links to fellow students anymore, since I don't study anymore. Also, mutexes are irrelevant because Lua isn't multithreaded and your boolean flag would work around your dirty flag's problem precisely as well as it would work around the problem without applying your dirty flag technique. [QUOTE=Sapd;51636276] Despite the problems we talked about, there is still another problem with this library which was not mentioned, this also brings us back to the main topic. The lack of support of transactions always makes things "non-deterministic" and more or less inconsistent, especially when using multiple threads. There also needs a way to get a specific connection to use transactions. Like: Such a mechanism would provide a real solution for multiple problems.[/QUOTE] I agree.
You're all arguing about a module that has origins from like 2009. Get off your high horse and use a different module for fuck sake. Holy shit I'm so glad I stopped playing gmod, because this is just reminding me why I even left in the first place. I thought I would be nice and update the module for someone who asked, but instead all I see is complaint after complaint. CLASSIC FACEPUNCH. Lock this thread and permaban me please. THANKS! [highlight](User was permabanned for this post ("Requested" - Sgt Doom))[/highlight]
[QUOTE=BlackAwps;51637459]You're all arguing about a module that has origins from like 2009. Get off your high horse and use a different module for fuck sake. Holy shit I'm so glad I stopped playing gmod, because this is just reminding me why I even left in the first place. I thought I would be nice and update the module for someone who asked, but instead all I see is complaint after complaint. CLASSIC FACEPUNCH. Lock this thread and permaban me please. THANKS![/QUOTE] The discussion should be a good inspiration for developers of other MySQL modules and those who want to make new ones. I accept that you won't support this anymore. The criticism against tmysql4 will remain until someone improves on it, if ever, and that's alright.
[QUOTE=BlackAwps;51637459]You're all arguing about a module that has origins from like 2009. Get off your high horse and use a different module for fuck sake. Holy shit I'm so glad I stopped playing gmod, because this is just reminding me why I even left in the first place. I thought I would be nice and update the module for someone who asked, but instead all I see is complaint after complaint. CLASSIC FACEPUNCH. Lock this thread and permaban me please. THANKS![/QUOTE]I wouldn't judge all of Facepunch by the Gmod section, but fair enough. Will prolly leave the thread open if there's further discussion to be had.
[QUOTE=Sgt Doom;51638111]I wouldn't judge all of Facepunch by the Gmod section, but fair enough. Will prolly leave the thread open if there's further discussion to be had.[/QUOTE] I would just close it at this point, the module has its fair share of issues, the creator doesn't support it anymore and there's a proper replacement plus the thread op is gone now too. [url]https://facepunch.com/showthread.php?t=1515853[/url]
[QUOTE=Leystryku;51641623]I would just close it at this point, the module has its fair share of issues, the creator doesn't support it anymore and there's a proper replacement plus the thread op is gone now too. [url]https://facepunch.com/showthread.php?t=1515853[/url][/QUOTE]Ah k, since there's a replacement then this thread no longer really serves a purpose.
Sorry, you need to Log In to post a reply to this thread.