virtual void modify() = 0;
ToohrVykMember Since 12 Sep 2002
Offline Last Active Jul 07 2012 12:09 PM
- Group Members
- Active Posts 9,683
- Profile Views 3,419
- Submitted Links 0
- Member Title GDNet+
- Age Age Unknown
- Birthday Birthday Unknown
- Website URL http://www.nicollet.net
Posted by ToohrVyk on 07 July 2012 - 12:08 PM
Posted by ToohrVyk on 03 January 2011 - 06:14 AM
When men first came to the valley of Wu, they were desperate and miserable, for they had no hides to shield them from the cold winters, no wings to escape the maws of nightly beasts and no roots to draw sustenance from the barren soil. Man, a creature of craft and tools, could not function without a complete infrastructure where the miner brought the ore to the smelter, who brought the iron to the smith, who brought the plow to the farmer, who tended the fields and provided pizza and coffee to the programmer, who wrote video games for them all.
And so, the ever-pervading Titans of Wu saw the desperation of these men, and pity entered their eternal hearts. Yojimbo, the Wooden Dragon, took upon himself to bring fresh wood to every man in the valley. Mishigo, the Sparkling Flame, brought fire to every hearth, warmth to every home and overclocking to every Pentium. Soon, every Titan found its way into the daily lives of the men of Wu.
Today, my blade sought the heart of Asuna, the Physics Engine, and found it.
"Who will provide our daily simulation steps, if not Asuna?" had they asked for centuries. Men had grown too dependent on the mighty Titan, scared as they were to do the work themselves. And so, as the small valley grew to a continent-spanning empire, the presence of a single Physics Engine grew increasingly inadequate. For when the wise sages of the east wanted to run a simulation, they had to wait for the clever smiths of the west to finish their own, and such woe came to those who had forgotten their cleanup steps that most of the time was spent in repetitive multi-threading incantations rather than actual simulation.
And so the time came to slay our former ally, for only its death could bring change to the World of Men. Like wind among the snow-covered BSP trees, our secret army flowed throughout the code base, spying on Asuna's every tendril with our refactoring tools, silently replacing her all-encompassing embrace with a rogue network of ersatz Physics Engine instances that were passed hand-to-hand to those who needed them. Static type-checking ensured that no man was left behind, and a small unit-testing task force was trained for the occasion.
Until that climactic battle. For Asuna noticed, and so great was her anger that countless access violations tore apart the fabric of the empire. Our spies and networks had provided everyone with the instances they needed, and our unit tests could detect the tremors in the Titan's footsteps, but the power of the Mighty Ones was beyond our imagination—for with their fingertips they commanded the powers of Unwritten Assumptions.
So concerned had we been with providing every man and woman with their own instance, that we had forgotten to check who expected to share an instance with another: everyone had their own instance now, but everyone expected the others to be using it as well. In the utter collapse of our civilization, we fought to bring together foreigners from distant realms who happened to work with our own people. Who would have expected that two people thousands of miles apart needed to use the exact same instance?
But the fight is over now. Dry tears mourn our fallen brothers of the Global Variable Inquisition, and a black wind rises from the east. Orochi, General of the All-Seeing Logging System, has roused from his slumber.
When you're writing code, you need to make assumptions or you will never ship anything. Some assumptions are pretty solid — "I will never have my code run on a Sega Genesis console" is as solid as it gets these days — but others are weaker. There's a whole spectrum of them actually, and you need to decide which deserve to be made and which should be avoided.
Having all your code share a certain instance is such an assumption. Sometimes, making it is the fastest path you can follow to your objective, and it would be silly to avoid it merely because it "feels" dirty, or someone told you it's a bad thing.
What you absolutely need to, however, is prepare a contingency plan. When (not if) that assumption is eventually broken, you will need reinforcements, because the last thing you want to do is backtrack on that assumption with your debugger. In fact, there's an entire scale of how easily you can backtrack on an assumption:
- Toggle a compiler option or runtime configuration setting. This is as cheap as it gets, so don't fret. Make that assumption and move on. Total time: five minutes. This is where "how much stack memory do I need?" assumptions live.
- Change a definition in your code or data files and recompile. This increases the possibility of error, and it requires sufficient knowledge of the code base to know that definition was there in the first place, but it's still a cakewalk. Total time: 15 minutes, including coffee. This is where "How much damage does a rocket do?" happens.
- Use automated refactoring tools. This is where "Do we call them _foo or m_foo?" happens. Total time: 30 minutes, mostly checking that the refactoring was correct.
- Do some manual refactoring using a simple deterministic set of rules, until the compiler stops complaining. This is what removing a global variable means if you've followed proper global usage patterns (more on this in a bit). Total time (from my experience): about an hour for a 20KLOC project. I do this about once a week (and I don't have unit tests, but I have excellent reasons for doing so).
- Do some manual refactoring using complex rules, until the unit tests stop complaining. This is what removing a global variable means if you were sloppy with your usage patterns. This might take you a day, possibly more—at this level, things are not predictable anymore.
- Read the code to reverse engineer the basic principles, write unit tests, rewrite significant portions. Could take up to a week and involve at least one team member flying into a murderous rage. This only really happens if your program is a mess and there's sticky goo floating all around the place.
So, what are these usage patterns I've been blubbering about?
One pattern is the (observationally equivalent) immutable value. If your global is a constant, or behaves like a constant for all means and purposes, it makes refactoring a lot easier, because it means that two distinct places in your code cannot communicate through that variable. Of course, it's still possible that the two places expect to receive the same constant (such as an array size), but you've eliminated a whole slew of potential issues. In short, if you can use a constant, do it. If data can be changed, but you can make it look like it never does from 99% of your code, do it and document with appropriate language features (const...)
Another pattern is the layered architecture. Make every piece of your code part of a layer, and then decide which layers are allowed to do what. MVC is a good example of this: the view cannot change the model data, and the model data cannot interact with the renderer. So, if the renderer was a global, and you needed to un-globalize it, you wouldn't need to worry about the model. As you write your game, you will end up with a lot more layers than just MVC, until it looks like a stack of thin pancakes such as "enemy controllers", "weapon views", "AI models" ... make sure you decide on coherent rules for every such layer as far as global access goes, including rules about when two elements may expect to be using the same instance or not.
Yet another pattern is the thousand faces instance—using polymorphism or encapsulation, hide a single instance behind several types that represent different aspects of what it can do. Even though right now a "Physics Simulator" and an "AI Physics Simulator" are the exact same thing (down to the method names and types), you can have one type evolve independently of the other and immediately track down where in your code it was used. Once you need to remove the global aspect, you can do so by applying rules on a per-type basis.
Ultimately, when a variable ceases to be global, you need to pass it down to every place that needs it, by making it a member variable of the objects that use it or an argument of the functions that use it (use whichever is appropriate on a case-by-case basis). Don't think that because an instance stops being a global variable, and becomes a member variable of a global variable, it's solved. Global is a different thing from "global variable". If you can access your object without passing it as an argument or creating it yourself, it's global even if it's not a global variable.
The bottom line is: are you comfortable with backtracking on your assumption ten days from now? Ten months from now? Do you have a documented contingency plan? If you're scared about it, backtrack now before it's too late.
Posted by ToohrVyk on 09 September 2010 - 11:11 PM
From a performance standpoint, in your typical PHP+MySQL web application, updating the game state on demand is an extremely bad idea, on par with holding a metal pole in a thunderstorm or bathing in shark-infested waters with a bathing suit made of fresh seal meat. You can try it, and you can make it work (if your server is hideously over-powered for the number of users you have), but it has severe scaling issues.
The first problem with on-demand updates has already been mentioned: it might take a while between sessions. The next user to come online has to suffer a huge performance hit to let the game server catch up with the current state. Not because the language is slow or because it's doing a+=1 five times instead of a+=5 once (all of this can be optimized, although not necessarily for free) but because a massively concurrent web application needs to save the data back to the database, and writing data to MySQL is insanely expensive. If your application has to perform even one indexed INSERT per interaction, then a single user catching up on all the interactions for an hour of idle time will have one hell of a wait time. And without one INSERT per interaction, good luck implementing the «here's what happened while you were away» feature on your game.
But that's not the worst issue (you can kind-of-solve-it by making sure someone requests an update every X seconds, just like the Original Poster is trying to solve it here).
The second problem with on-demand updates is stampeding (or serialization, depending on how you look at it). Stampeding basically happens like this:
- User A connects to the server, notices that some updates should be done, and starts performing them transactionally.
- User B connects to the server. Since User A is acting transactionally, User B sees the same needs-to-be-updated game state and starts updating it transactionally. This doubles the performance load of the update.
- More users connect. Since the performance load increased, the updates take longer, so there's a higher probability of starting an upload that is already being processed. The server load increases until it basically gives up and takes its own life in frustration (yes, MySQL does that).
Depending on what your updates look like and how your transaction isolation level is configured, you might experience serialization instead: User A connects and does an update. User B connects and reads the "Todo" table(s) but is locked out because User A is working on it, so User B idles until User A is done. The lock on the "Todo" table(s) means your server can now only serve one user at a time. This is a major performance problem if you intend to serve more than a handful of simultaneous users, because locking problems can not be solved by adding more hardware.
The ideal architecture for a web game is to have a single update "thread" (might be a cron job or a script running without a time limit) that polls the database for things to be done and does them, and any number of threads to serve the HTTP requests from the players by reading data from the database and using lock-free, possibly delayed, cluster-indexed INSERTs to tell the update thread what to do. If you have the money, replicate the main database on another server and have all the players read from the slave to lighten the load and read locks on the master (if the server holding the update thread is powerful enough, you can scale your game to infinity). If you don't have that kind of money, and you see the update thread getting locked out too often, you might want to set lower a thread isolation level (READ COMMITTED) to reduce that (at the cost of sometimes displaying some inconsistent data on the user's screens).
In practice, it also helps if the game is designed so that most updates cost nothing. This can be done with two techniques:
- Do not compute or store anything the game logic does not need. If you don't need it to run the game logic, then you can compute it on demand when (and if) the user asks to see it and discard it afterward. For instance, if your units gain ranks based on the number of enemies they kill, then you do need to compute and store the number of kills, but you don't have to find out the name of the current rank until the user needs to see it.
- Use "do not open until" timestamps. Instead of the user inserting a "Build Space Station" order in an order table, they should instead insert a "Space Station" building in the building table with a timestamp of NOW() + buildtime. This means the act of successfully building the space station does not involve any computation (at the cost of an additional timestamp test when reading from the building table, but that can be easily indexed).
These will decrease the load on your server.
By the way, the default time limit for a PHP script running in CLI is infinite (and defaults to 30 seconds for CGI and Apache mod_php), so you don't have to configure anything.
Posted by ToohrVyk on 12 January 2009 - 10:10 PM
I would tend to question your reliance on tutorials, though, because it has two negative effects. First, a tutorial can only help you achieve what that tutorial is about, so you won't be able to create your own things with only tutorials. Second, a tutorial only teaches you what you need to run it, and if your project needs other things, the tutorial will not mention them.
Even then, Flex 3 + ActionScript 3 has a nice online manual, including a part about using Flash 9 to draw things. The manual is both concise and in-depth, and you can rely on it fully to develop a complete game without ever having to resort to third party material.
Posted by ToohrVyk on 22 January 2008 - 02:02 AM
You know what's modular in the real world? Condoms. They can be used as a contraceptive, they can be used to prevent STDs, they can be used as a barrel plug on paintball guns, they can be used to protect a live gun barrel from moisture when wading through rivers, they can be used to smuggle liquids or powders, and so on. And what makes condoms so modular in the first place?
- Easy to set up. Rip the pack open, unfold, perform task. You should nt have to go through a dozen hoops and fill ten forms to get a working object.
- Simple and general concept. It's an elastic, impermeable, transparent pocket. Although its primary use (the one it's optimized for) is prevention of insemination, it is not restricted to doing only that. In the same way, when you design a portion of your game, determine if the code you are writing is not overly restrictive. Perhaps it could be used to do other similar things in other places?
- High availabiliy. Condoms are extremely cheap and easy to get your hands on, which proves they have not been designed by your average software engineer. I can see it now:
«The user will only need one condom to get his thing on, right?
— Right. Singleton?
The fundamental point to condoms is that you can get as many as you need. If you design your objects in terms of "you only need one" then by design you are restricting reuse.
- Standalone. It doesn't require other things to work. You don't have to plug it in. You don't have to use an Adapter pattern to fill it with distilled vodka. You don't have to set up a render-target window and you don't have to run a network thread in the background. Have your objects work on their own, like a condom: a condom needs itself, and an object to be placed around. Your modules should need themselves, and a target task to be placed around.
Posted by ToohrVyk on 10 November 2007 - 09:09 PM
Posted by ToohrVyk on 11 September 2007 - 12:11 AM
The advantages of anonymous namespaces are that you don't have link-time collisions (because your identifiers are restricted to a single file) and don't pollute headers with implementation details (like a static class member or nested class would).
Posted by ToohrVyk on 01 February 2005 - 09:26 PM
what's wrong with being a C++ zealot? OOP, once understood, makes life so clean and simple ;)
C++ is a language, OOP is a programming paradigm. C++ happens to natively implement a (limited) version of OOP, but this is the only relationship between the two. OOP does not require fancy "class" keywords in the language to be used, it only requires the existence of a linking step (and I'm not even sure THAT is required). You can do OOP with any language, including C, so it's not legitimate to use OOP as a justification for using C++