Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.
Forumwarz and RJS
A love/hate affair
An internet-based game
  about the internet
LOL WHUT?
The Internet is a
wonderful, magical place!
But it’s also terrible.
Very, very terrible.
John Gabriel’s Greater Internet Fuckwad Theory
Exhibit A
Have you heard of it?
• One of the most popular forums in the
  world
• Almost all its users are anonymous
• Unfortunately...
The anonymity of the
Internet means you can
     be anything...
You can be another ethnicity!
You can be another species!
You can even be beautiful!
(Even if you’re not.)
Are you going to explain
 what forumwarz is, or
 just show us hackneyed
 image macros all night?
Role-Play an Internet User!




 Camwhore   Emo Kid   Troll
Role-Play an Internet User!
  • Each player class features unique abilities
    and attacks
  • Many different ways to pla...
An interface heavy in RJS

    (please endure this short demo)
Let’s get technical
Forumwarz Technology
Some of our stats
• ~30,000 user accounts since we launched
  one month ago
• 2 million dynamic requests per day (25-55
  ...
Deployment
• Single server: 3.0Ghz quad-core Xeon, 3GB
  of RAM
• Nginx proxy to pack of 16 evented
  mongrels
• Modest-si...
Thank you AJAX!
• One reason we can handle so many
  requests off a single server is because
  they’re tiny
• We try to le...
why we   rjs
A simple example
Example View
 #battle_log
   There's a large monster in front of you.

 = link_to_remote quot;Attack Mons...
Pretty cool eh?
• Without writing a line of javascript we’ve
  made a controller respond to an AJAX
  request
• It’s fast....
* but it can haunt you
Problem #1: Double Clicks
 • Often, people will click twice (or more!) in
   rapid succession
 • Your server gets two requ...
A Solution?
• Use some javascript to prevent multiple
     clicks on the client side
 var ClickRegistry = {
   clicks : $H...
A Solution?
• Add a helper, link_once_remote
def link_once_remote(name, options = {}, html_options = {})

 click_id = html...
Our Example: v.2
Example View
 #battle_log
   There's a large monster in front of you.

 = link_once_remote quot;Attack Mo...
Surprise!




It doesn’t work!
Why not?
• Proxies or download “accelerators”
• Browser add-ons might disagree with the
  javascript
Also, it’s client validated!
• Let’s face it: You can never, ever trust client
  validated data
• Even if the Javascript w...
Server Side Validation
• It’s the Rails way
• If it fails, we can choose how to deal with
  the invalid request
  • Someti...
Problem #2:Validations
• ActiveRecord validations can break during
  concurrency
• In particular, the validates_uniqueness...
The Uniqueness Life-Cycle
  select * from battle_turns where turn = 1
  and user_id = 1;
  if no rows returned
     insert...
Transactions don’t help
• With default isolation levels, reads aren’t
  locked
• Assuming you have indexed the columns in
...
A solution?
• Could monkey patch ActiveRecord to lock
  the tables
• That’s fine if you don’t mind slowing your
  database ...
A different solution?
• You can rescue the DB error, and check to
  see if it’s a unique constraint that’s failing
• This ...
Problem #3: Animation
• script.aculo.us has some awesome
  animation effects, and we use them often.
• RJS gives you the g...
When order matters
• Often you’ll want to perform animation in
  order
• RJS executes visual effects in parallel
• There a...
Effect Queues
• You can queue together visual effects by
  assigning a name to a visual effect and a
  position in the que...
page.delay
   page.visual_effect :fade, 'toolbar', :duration => 1.5
   page.delay(1.5) do
     page.call 'Toolbar.maintena...
It’s me again!




This also doesn’t work!
Durations aren’t guaranteed
  • Your timing is at the whim of your client’s
    computer
  • Your effects can step on each...
A solution?
def visual_effect_with_callback_generation(name, id = false, options = {})
  options.each do |key,value|
    i...
And then, in RJS
page.visual_effect :fade, 'toolbar', :duration => 1.5, :afterFinish => lambda do |step2|
  step2.call 'To...
in Conclusion
Nobody’s Perfect!
Nobody’s Perfect!
• We love RJS despite its flaws
• It really does make your life easier, most of
  these issues would neve...
this presentation was
  brought to you by
this presentation was
  brought to you by


   Any questions?
Forumwarz and RJS: A Love/Hate Affair
Próxima SlideShare
Cargando en…5
×

Forumwarz and RJS: A Love/Hate Affair

1.424 visualizaciones

Publicado el

A presentation given at TSOT rails project night about Forumwarz, and some of the issues we faced with our web based rpg with RJS.

Publicado en: Empresariales, Tecnología
  • Sé el primero en comentar

  • Sé el primero en recomendar esto

Forumwarz and RJS: A Love/Hate Affair

  1. 1. Forumwarz and RJS A love/hate affair
  2. 2. An internet-based game about the internet
  3. 3. LOL WHUT?
  4. 4. The Internet is a wonderful, magical place!
  5. 5. But it’s also terrible.
  6. 6. Very, very terrible.
  7. 7. John Gabriel’s Greater Internet Fuckwad Theory
  8. 8. Exhibit A
  9. 9. Have you heard of it? • One of the most popular forums in the world • Almost all its users are anonymous • Unfortunately, it’s hilarious.
  10. 10. The anonymity of the Internet means you can be anything...
  11. 11. You can be another ethnicity!
  12. 12. You can be another species!
  13. 13. You can even be beautiful!
  14. 14. (Even if you’re not.)
  15. 15. Are you going to explain what forumwarz is, or just show us hackneyed image macros all night?
  16. 16. Role-Play an Internet User! Camwhore Emo Kid Troll
  17. 17. Role-Play an Internet User! • Each player class features unique abilities and attacks • Many different ways to play • A detailed story line ties it all together
  18. 18. An interface heavy in RJS (please endure this short demo)
  19. 19. Let’s get technical
  20. 20. Forumwarz Technology
  21. 21. Some of our stats • ~30,000 user accounts since we launched one month ago • 2 million dynamic requests per day (25-55 req/s) • Static requests (images, stylesheets, js) are infrequent, since they’re set to expire in the far future • About 25GB of bandwidth per day
  22. 22. Deployment • Single server: 3.0Ghz quad-core Xeon, 3GB of RAM • Nginx proxy to pack of 16 evented mongrels • Modest-sized memcached daemon
  23. 23. Thank you AJAX! • One reason we can handle so many requests off a single server is because they’re tiny • We try to let the request get in and out as quickly as possible • RJS makes writing Javascript ridiculously simple
  24. 24. why we rjs
  25. 25. A simple example Example View #battle_log There's a large monster in front of you. = link_to_remote quot;Attack Monster!quot;, :action => 'attack' Example Controller def attack @monster = Monster.find(session[:current_monster_id]) @player = Player.find(session[:current_player_id]) @player.attack(@monster) update_page_tag do |page| page.insert_html :top, 'battle_log', :partial => 'attack_result' end end
  26. 26. Pretty cool eh? • Without writing a line of javascript we’ve made a controller respond to an AJAX request • It’s fast. No need to request a full page for such a small update • It works great*
  27. 27. * but it can haunt you
  28. 28. Problem #1: Double Clicks • Often, people will click twice (or more!) in rapid succession • Your server gets two requests • If you’re lucky they will occur serially
  29. 29. A Solution? • Use some javascript to prevent multiple clicks on the client side var ClickRegistry = { clicks : $H(), can_click_on : function(click_id) { return (this.clicks.get(click_id) == null) }, clicked_on : function(click_id) { this.clicks.set(click_id, true) }, done_call : function(click_id) { this.clicks.unset(click_id) } }
  30. 30. A Solution? • Add a helper, link_once_remote def link_once_remote(name, options = {}, html_options = {}) click_id = html_options[:id] || Useful.unique_id options[:condition] = quot;ClickRegistry.can_click_on('#{click_id}')quot; prev_before = options[:before] options[:before] = quot;ClickRegistry.clicked_on('#{click_id}')quot; options[:before] << quot;; #{prev_before}quot; if prev_before prev_complete = options[:complete] options[:complete] = quot;ClickRegistry.done_call('#{click_id}')quot; options[:complete] << quot;; #{prev_complete}quot; if prev_complete link_to_remote(name, options, html_options) end
  31. 31. Our Example: v.2 Example View #battle_log There's a large monster in front of you. = link_once_remote quot;Attack Monster!quot;, :action => 'attack'
  32. 32. Surprise! It doesn’t work!
  33. 33. Why not? • Proxies or download “accelerators” • Browser add-ons might disagree with the javascript
  34. 34. Also, it’s client validated! • Let’s face it: You can never, ever trust client validated data • Even if the Javascript worked perfectly, people would create greasemonkey scripts or bots to exploit it • Our users have already been doing this :(
  35. 35. Server Side Validation • It’s the Rails way • If it fails, we can choose how to deal with the invalid request • Sometimes it makes sense to just ignore a request • Other times you might want to alert the user
  36. 36. Problem #2:Validations • ActiveRecord validations can break during concurrency • In particular, the validates_uniqueness_of validation
  37. 37. The Uniqueness Life-Cycle select * from battle_turns where turn = 1 and user_id = 1; if no rows returned insert into battle_turns (...) else return errors collection
  38. 38. Transactions don’t help • With default isolation levels, reads aren’t locked • Assuming you have indexed the columns in your database you will get a DB error • So much for reporting errors to the user nicely!
  39. 39. A solution? • Could monkey patch ActiveRecord to lock the tables • That’s fine if you don’t mind slowing your database to a crawl and a ridiculous amount of deadlocks
  40. 40. A different solution? • You can rescue the DB error, and check to see if it’s a unique constraint that’s failing • This is what we did. It works, but it ties you to a particular database def save_with_catching_duplicates(*args) begin return save_without_catching_duplicates(*args) rescue ActiveRecord::StatementInvalid => error if error.to_s.include?(quot;Mysql::Error: Duplicate entryquot;) # Do what you want with the error. In our case we raise a # custom exception that we catch and deal with how we want end end end alias_method_chain :save, :catching_duplicates
  41. 41. Problem #3: Animation • script.aculo.us has some awesome animation effects, and we use them often. • RJS gives you the great visual_effect helper method to do this: page.visual_effect :fade, 'toolbar' page.visual_effect :shake, 'score'
  42. 42. When order matters • Often you’ll want to perform animation in order • RJS executes visual effects in parallel • There are two ways around this
  43. 43. Effect Queues • You can queue together visual effects by assigning a name to a visual effect and a position in the queue. • Works great when all you are doing is animating • Does not work when you want to call custom Javascript at any point in the queue • Unfortunately we do this, in particular to deal with our toolbar
  44. 44. page.delay page.visual_effect :fade, 'toolbar', :duration => 1.5 page.delay(1.5) do page.call 'Toolbar.maintenance' page.visual_effect :shake, 'score' end • Executes a block after a delay • If paired with :duration, you can have the block execute after a certain amount of time
  45. 45. It’s me again! This also doesn’t work!
  46. 46. Durations aren’t guaranteed • Your timing is at the whim of your client’s computer • Your effects can step on each other, preventing the animation from completing! • They will email you complaining that your app has “locked up”
  47. 47. A solution? def visual_effect_with_callback_generation(name, id = false, options = {}) options.each do |key,value| if value.is_a?(Proc) js = update_page(&value) options[key] = quot;function() { #{js} }quot; end end visual_effect_without_callback_generation(name, id, options) end alias_method_chain :visual_effect, :callback_generation Thanks to skidooer on the SA forums for this idea!
  48. 48. And then, in RJS page.visual_effect :fade, 'toolbar', :duration => 1.5, :afterFinish => lambda do |step2| step2.call 'Toolbar.maintenance' step2.visual_effect :shake, 'score' end • The lambda only gets executed after the visual effect has finished • Doesn’t matter if the computer takes longer than 1.5s
  49. 49. in Conclusion
  50. 50. Nobody’s Perfect!
  51. 51. Nobody’s Perfect! • We love RJS despite its flaws • It really does make your life easier, most of these issues would never be a problem in a low traffic app or admin interface • The solutions we came up with are easy to implement
  52. 52. this presentation was brought to you by
  53. 53. this presentation was brought to you by Any questions?

×