• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
Bow_vernon

Ways to avoid the needs and the musts of global variables??

87 posts in this topic

Quote:
Original post by phantom
You know, this makes me wonder how 'rnd' would react when being called from multiple threads at around the same time?
I wonder if the CRT is thread safe in that case...


Thread safety is not something that needs to be checked for or handled in code. That's a documentation issue.

For example, the .NET CRT specifies that all static functions are thread safe, member functions aren't. If you call a instance functions from different threads, that's on you. (Well debug mode will probably catch you because they put checks in for it). In C, rand() just isn't thread safe, and they're not babying you if you forget.

So if you want your logger to be thread safe, you document that, and put locks/etc INSIDE the module. The interface doesn't change and the calling code knows nothing about it.

Quote:
Original post by return0
Or, store the state in private member variables of an object, "init" it in the constructor, "shut_down" in the destructor/dispose, and have a well defined API. Create these objects where needed. Then invert that dependency.

Now it's impossible to call "Log" before "Init". Yay!


What? My modules often have a static bool init (or more often a state enum) that you can check in your global functions to make sure that the module is in the proper state to execute the function.

It doesn't need to be a "private member variable of a global singleton instance" to do that.
0

Share this post


Link to post
Share on other sites
Quote:
Original post by CadetUmfer
In C, rand() just isn't thread safe, and they're not babying you if you forget.


Sorry, I probably wasn't being clear; the quoted bit was what I was simply pondering on. I'd never looked into what the C Runtime Time had to say with regards to thread safety of that function and had never considered the 'static' nature of its operation in that way.

Nothing more than a pondering on my part [smile]

(For the record, when it comes to designing my own stuff I'm well aware of the thread safety requirements of things [smile])

0

Share this post


Link to post
Share on other sites
Quote:
Original post by CadetUmfer
Quote:
Original post by phantom
You know, this makes me wonder how 'rnd' would react when being called from multiple threads at around the same time?
I wonder if the CRT is thread safe in that case...


Thread safety is not something that needs to be checked for or handled in code. That's a documentation issue.

For example, the .NET CRT specifies that all static functions are thread safe, member functions aren't. If you call a instance functions from different threads, that's on you. (Well debug mode will probably catch you because they put checks in for it). In C, rand() just isn't thread safe, and they're not babying you if you forget.

So if you want your logger to be thread safe, you document that, and put locks/etc INSIDE the module. The interface doesn't change and the calling code knows nothing about it.

Quote:
Original post by return0
Or, store the state in private member variables of an object, "init" it in the constructor, "shut_down" in the destructor/dispose, and have a well defined API. Create these objects where needed. Then invert that dependency.

Now it's impossible to call "Log" before "Init". Yay!


What? My modules often have a static bool init (or more often a state enum) that you can check in your global functions to make sure that the module is in the proper state to execute the function.

It doesn't need to be a "private member variable of a global singleton instance" to do that.

I'm not advocating a singleton, or a global instance. That would be insane. I'm advocating using an object, and injecting it where required. Then you don't need to "check static state flags" in global functions because you know it's in a good state. The runtime guarantees it.
0

Share this post


Link to post
Share on other sites
Quote:
Original post by phantom
- Design Patterns; seriously, stop even trying with this you are just showing your ignorance constantly. Design Patterns are nothing more than a common language used to talk about things. A good programmer would be aware of them but wouldn't say "I need to do X, what pattern can I use?" they would say "I need to do X, how can I solve it?" and then later, when discussing it with someone else might say "Yeah, I used a flyweight for that" or "I hooked it up as an observer to do....".


Just going to jump in on this point. I'm currently in my 3rd year of a computer science degree at a university I'd consider decently reputable. Given what I've seen of my fellow students, many of them probably WOULD fall into the sort of thinking that results in "I need to do X, what pattern can I use?" In fact, I think I've overheard almost exactly that quote during an in-class exercise, and both my midterm and final in my 3rd-year level software engineering course had an exam question (if not more than one!) that was basically "you need your program to do X, which of the following patterns would be best used here?" Now, mind you, the professor never explicitly encouraged this kind of thinking - but it would be difficult, given the material presented, to develop any other kind.

If this goes on at other institutions, then Rubicon's example, though obviously exaggerated and invalid at face value, is nevertheless an accurate portrayal of the way many of these people think. If my experience is anything to judge by, then many new graduates are not going to realize that design patterns are descriptive instead of prescriptive. My point is that if Rubicon's sole experience with "design patterns" is through these graduates, I would say that he's not to entirely blame for his warped perspective of them and the distaste for them that accompanies it. One might argue that he is to blame for not going out and educating himself on the matter, however. I had much the same view of design patterns before I learned better (by reading a thread here on GDNet, oddly enough).
1

Share this post


Link to post
Share on other sites
Quote:
Original post by return0
I'm not advocating a singleton, or a global instance. That would be insane. I'm advocating using an object, and injecting it where required. Then you don't need to "check static state flags" in global functions because you know it's in a good state. The runtime guarantees it.

Sure that's great. But we're talking about replacing something that was in global scope. The decision to put it in global scope was probably made because so many objects need access to it.

Yeah, you can have one single instance (managed/owned sanely, not as a singleton) and pass that around if you need that flexibility. But if that flexibility isn't worth it, use global functions with local state.

I equate it to logging in .NET. There are lots of nice logging frameworks that will do all sorts of cool stuff via dependency-injection. There's also Trace.WriteLine, which will write to a file or a console or a custom TraceListener that you make, so it covers 80% of the use cases.
0

Share this post


Link to post
Share on other sites
Quote:
Original post by return0
ApochPiQ, I think I am more aligned with your thoughts on engineering but I don't think you're necessarily being fair on Rubicon.


No, he is being absolutely fair. Perhaps Rubicon does not understand how he comes across, but here is the typical exchange between him and gd.net posters:

Poster: Practice A is good because of X, Y, and Z.
Rubicon: Practice A is shite and a waste of time.

Not once does he not address the actual points raised by the poster, but his response is often based on ignorance of the actual subject. I have seen multiple threads where he admits not understanding the basic terminology concerning PLM and modern best practices, nor does he understand their application, yet he attacks them anyway. Not with well thought out counter arguments, but with rhetoric as above where he denounces them as worthless and not deserving of further discussion.

Not once in his thread has he posted a productive criticism of unit testing and continuous integration. He has fallen back on the age old tactic of those who refuse to educate themselves on a topic by declaring it a waste of time, thus ending any possible debate in the issue.

Rubicon, I have worked with many programmers like you. They are good, even great at many things. But they steadfastly refuse to educate themselves on new software engineering practices simply because they believe the way they have done things is the best and only way to continue to do them. Not only do they not educate themselves, but they feel free to share their ignorant opinion with everyone on how much of a waste of time these new-fangled concepts are.

Even if they are right, it would still make for a much better conversation if they actually educated themselves before having an opinion, and offered well thought-out criticisms rather than dismissals. Even just entering the debate honestly with an open mind and responding directly to a logical argument with a coherent reply would make interacting with these individuals a million times more pleasant.

Your experience is not the sum-total of all programming experience. In fact, I would be so bold as to declare that your experience is quite narrow in relation to many other people in the industry. For example, I have worked on: a mission-critical enterprise application for deployment in data centers, customer critical software for medical robotics, simulation software under government contract, and multiple POC demonstrations ranging from simulations to SOA app development. All of these projects involved different technologies, languages, APIs, PLMs, configuration systems, PM objectives, acceptable risk levels, etc. etc. I have worked under more development process requirements than most people encounter in a lifetime. I walked into each job with an open mind, even about things that I personally believed to be a waste of time, and most of the time I found out I was wrong.

And even through all this, I don't believe I am better that you. In fact, I can probably learn a thing or two from you. But that is only because I would approach you with an open mind.

Edit: Arg, I hope I am not beating a dead horse, didn't realize I was so late to the game here. Rubicon, please don't take this as an insult, as I clearly only wish for you to see things from another perspective.
0

Share this post


Link to post
Share on other sites
This reminds me of one of my coworkers. Thinks he's hot shit because he's been a professional for 20+ years and can write some working code quick in 15 year old technology. Sadly, it's not nearly quick enough, and it's not nearly up to the coding standards of the shop (mildly cutting edge C# with some agile voodoo in there). Programmers need to adapt, even the good ones.

Even more sadly, this reminds me an awful lot of me. Stuff works now, why change it? I can code that in 15 minutes, why spend the 30 screwing around with some ill-documented library?

And I can certainly relate to (and generally advocate) a... more moderate version of Rubicon's stance. Too often I've seen people write unit tests which are more complex than what they're testing; something that might be unlikely to change; something that might be low impact if it does break. Too often I've been wrangled by PMs into the next big software engineering methodology that claims to break human nature...

It sucks. But let's face facts here: software engineering is immature. It's going to get better. Languages and libraries can let us do things now in a few minutes that was a few years to write even a decade ago. If you sit there resting on your laurels, you're only harming yourself.
0

Share this post


Link to post
Share on other sites
You know, all these assmumptions about what I do or don't know about, picking apart statements that are obviously meant to be illustrative and the general ill feeling I'm getting is just getting old now. Especially the bit where I get slagged off for presenting my ideas abruptly yet I'm meant to just shrug off being called, sick was it?

Maybe I should go back to school, learn twenty new tricks and start using unneeded language features just to fit in here better, I just don't know.

A couple of large studios still program in vanilla C and one of those is all over the charts at the moment. And I learned my trade writing 6502 assembler - there wasn't much call for boost back then.

Time's have move on, sure. But the basic facts remain. You do not need to spend a couple of years learning about side issues to write good games. And you can slag me off all day for my outdated luddite views but you won't change that fact.

I hope I can keep my own company running well, because I've been out of the employed workspace now for eleven years. If it really does look like the picture I'm getting painted here, then I see now why there are so many company failures and why all the top-drawer people are leaving the industry in spades. I'm gonna find it real hard to get a job anyway because I just don't know anything relevant, my views on coding are just shite, I can't hold a conversation let alone get along with people and I have pointy ears. Scary prospect.

Game over man.

[Edited by - Rubicon on January 3, 2011 6:32:12 AM]
0

Share this post


Link to post
Share on other sites
Quote:
Original post by Rubicon
You know, all these assmumptions about what I do or don't know about...


We have to make assumptions about what you do and don't know, because that's all the information we have. All we've got to go by is your own word on these forums. And when you've admitted to never even hearing of "waterfall" development before, why should we assume you actually have any education of value when it comes to practices and policies in software engineering? You've said yourself in this very thread that you don't know about certain concepts; in fact at one point you flippantly dismissed a concept in the same breath as saying you knew little about it! (Please don't tell me I have to quote your own post to you again.)

What are we supposed to think here? Seriously. What are we supposed to do, if not take your words for what they appear to mean, and draw the obvious conclusions?


Quote:
... picking apart statements that are obviously meant to be illustrative...


No, it is NOT "obvious". If it were, nobody would be getting all pedantic about it. The fact of the matter is that this is a textual medium, and all we can go by is the literal interpretation of your exact words. When you say things like "fuck the standards committees" how are we supposed to infer that you're just "kidding" or "being illustrative" or whatever you want to write it off as?

Maybe we're getting an incorrect reading on you, who you are, how you actually think. But you've got only yourself to blame for that, because you are the only person who controls how you portray yourself here. If you give off a particular image, and we react negatively to that image, and you don't like our reaction... it isn't our problem - it's yours for giving off that image in the first place. Fix the way you portray yourself - or just fix yourself, whichever is actually appropriate in your particular situation - and you'll get better reactions from people.

Quote:
... the general ill feeling I'm getting is just getting old now.


I can't speak for anyone else, but I can speak for myself.

I have no ill feeling for you. I find your attitude distasteful and your methods of communication immature and harmful to impressionable beginners, but I'm not trying to crucify you or chuck you off a bridge here. I'm being harsh in my response to you out of my own imperfection and impatience, but I'm responding because I honestly would like to see you improve as both a person and a programmer.

I think it's a damned shame that the industry loses so much knowledge and talent to age and turnover, and it's always nice to see people who have weathered the storm for a long time and been successful. What bothers me is when those people show signs of significant flaws in their thinking and/or character which could easily be addressed.

What do I want here? I don't want to see you pissed off, or leave in a huff, or whatever. I don't even particularly care if you agree with me on the engineering side of things. I want you to understand that you're coming off as a self-righteous know-it-all and that that is detrimental, not just to the discussion at hand, but to the community as a whole - and to yourself.



Quote:
Especially the bit where I get slagged off for presenting my ideas abruptly yet I'm meant to just shrug off being called, sick was it?


Nobody is "slagging you off" for being abrupt. We're getting on your case for being rude, abrasive, caustic, and dismissive - more importantly, for doing so in areas where, by your own admission, you are hardly qualified to act as an authority.

This is, fundamentally, a place of learning. And if you're teaching new students to do things that we consider dangerous, of course we're going to jump on you. It's out of protectiveness for those who don't know any better, not out of antagonism for you personally.

And nobody ever called you "sick." The closest thing I can find in this thread is where I said your attitude makes me feel slightly sick. Big difference.

For someone so obsessed with perfect reading comprehension and not "reading between the lines" or whatnot, you seem to have made a rather ironic mistake.

Either that, or you're just being melodramatic and hoping for pity. Hard to tell.


Quote:
Maybe I should go back to school, learn twenty new tricks and start using unneeded language features just to fit in here better, I just don't know.


If you do it to fit in, you're doing it for the wrong reasons.

If you do it to educate yourself, to better yourself, and to acknowledge that all of us are imperfect and none of us knows everything, then more power to you. I'll happily buy you a beer when you graduate.


Quote:
A couple of large studios still program in vanilla C and one of those is all over the charts at the moment. And I learned my trade writing 6502 assembler - there wasn't much call for boost back then.


There are reasons for those decisions, reasons which have basically zero bearing on the average hobbyist in his home building games for fun. Again, this is what bothers me about you. You just have to demand that everyone else does it your way, because it's good enough for you. Not everyone is in your circumstances. Not everyone is you. Other people may find benefit in things where you do not. This does not make them wrong.


Quote:
Time's have move on, sure. But the basic facts remain. You do not need to spend a couple of years learning about side issues to write good games. And you can slag me off all day for my outdated luddite views but you won't change that fact.


You can write a good game - even a popular, successful one - without knowing what you are doing.

But it'll bite you in the ass if you expect to be able to be that laissez faire with everything in the programming world, even games. Ever hear of Minecraft?


Quote:
I can't hold a conversation let alone get along with people and I have pointy ears.


We're trying to point out here that your getting along with people is contingent on your own behavior. Moreover, we're trying to point out that with a couple of minor attitude adjustments, you could find yourself in a much more pleasant situation overall.


And pointy ears aren't all bad. I mean, you could always look into Night Elf modeling agencies or something.
0

Share this post


Link to post
Share on other sites
Please stop ffs. I've admitted defeat. I know it's fun to kick a man when he's down, but the full nine pints is out already.

I already get that I'm a nasty, ill-educated luddite with no skills to bring to the table and whose opinion has no worth whatsoever. Don't worry about me, I'll keep muddling along somehow. So I guess that would be the melodrama option then.

[Edited by - Rubicon on January 3, 2011 12:02:16 PM]
0

Share this post


Link to post
Share on other sites
I sheathe my sword in the reddish light of a bloodied twilight, neither proud nor ashamed of what had to be done. To my left, Tezuma the Ever-Vigilant runs a few unit tests, going through the motions without really expecting any to fail. Sitting on a moss-covered rock nearby, Matsuru treats her wounds—a stray fang from the beast's death throes, envenomed with the bitter ichor of static initialization order, found its way through her statically typed armor.

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

Share this post


Link to post
Share on other sites
Quote:
Original post by ApochPiQ
Quote:
Original post by ToohrVyk
[Sheer, unbridled brilliance]
[Slow applause]
Amen.
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0