Samuel Williams

Keynote: Leveraging Falcon and Rails for Real-Time Interactivity

RubyKaigi 2024

00:00:13.240 Step back in time with me and witness a groundbreaking moment in digital communication. Watch as we connect the first computerized bulletin board system, or BBS. Through this pioneering technology, users could connect computers over a copper telephone line and interact using a text-based interface.
00:00:20.519 This was not merely a novelty; it was a revolutionary development that allowed individuals to collaborate and build communities in ways that were previously unimaginable. The sound you are hearing is the modulation of a digital signal sent over an analog telephone line.
00:01:22.240 These sequences of noises, familiar to many of us who grew up in the early days of computer technology, represent the moment of a digital handshake between two machines. Each beep was a signal of an impending connection, a portal opening to a world of information and interaction that was once groundbreaking. This sound marked the beginning of countless online adventures and discoveries, resonating deeply as a nostalgic echo of my childhood and perhaps yours as well.
00:01:53.040 Hello everyone, my name is Samuel Williams, and I'm truly honored to speak with you today about leveraging Falcon for real-time interactivity. In this talk, we'll first explore the origins of real-world computer networks and examine how early web gaming capitalized on evolving technologies to deliver engaging experiences. Then, we'll look at the origin of modern web technology, focusing on how Ruby on Rails has been utilized and the inherent challenges with real-time interactivity.
00:02:15.640 Following this discussion, we'll explore the evolution of Ruby, including the introduction of async, and how this provides a foundation for scalable web applications. Finally, we'll bring this all together and examine a practical example that combines the Falcon web server with the Rails framework, providing a taste of their combined potential in real-world scenarios.
00:03:03.760 In 1978, Chicago was struck by a massive snowstorm that brought the city to a standstill. Among those affected were Christian and some members of the Chicago Area Computer Hobbyists Exchange, also known as CACH. Trapped by the severe weather and unable to attend their usual in-person meetings, they found themselves with an unexpected abundance of free time. Previously, Christensen had developed a program called Modem.ASM, which enabled two computers to exchange files using modems connected over a copper telephone line. This allowed for real-time point-to-point communication, but required a user at each computer to run the program. Inspired by the challenges posed by the snowstorm, they saw an opportunity to leverage this communication mechanism to automate the distribution of club announcements.
00:04:47.720 Reflecting on the traditional corkboards at the CACH meetings, Christensen developed the first computerized bulletin board system. Using a basic programming language, he created an interactive text-based program that enabled members to dial in from their own computers to read and post club announcements. While Christensen focused on the software, he assembled an S100 computer to host the program. Remarkably, they had a working version within two weeks; however, they later claimed it took four weeks to avoid any perception that the project was rushed. They used a DC Hayes 80103 modem, one of the earliest modems compatible with the S100 computer, featuring the innovative Hayes command set. The smart modem could be controlled via computer commands, allowing for automated dialing and call management.
00:05:58.760 The computerized bulletin board system was a resounding success, reportedly handling 250,000 calls throughout its lifespan. As the first of many such systems, it not only played a crucial role in connecting users and communities but also generated significant excitement. This enthusiasm inspired others around the world to create and run their own BBS systems, laying the foundation for modern digital communication.
00:06:05.720 As you might imagine, the technology continued to evolve, and by 1984 it had paved the way for Chris Sherik to release the original multiplayer space game, Trade Wars. One of Sherik's earliest experiences with BBS systems was hacking into the TRS-80 Model 2 in his hometown of Sparks, Nevada. He posted a message to the system admin about a security hole, and the two became friends. Sherik was allowed to experiment with the BBS software, where he programmed the original Trade Wars game. At that time, according to Sherik, there was no multiplayer BBS, and he thought it would be a neat idea to use BBSs to play games against other people.
00:08:05.680 This is a screenshot that shows a recent version of Trade Wars, which became a foundational part of early online gaming, allowing players to engage in commerce, combat, and exploration within a vast text-based universe. Trade Wars not only capitalized on the interactivity of the bulletin board systems but also expanded the possibilities of what multiplayer games could achieve, setting a precedent for future development in online gaming. Sherik was motivated by the BBS technology initially developed for simple message exchanges among users, and it quickly became an inspirational technology for early online gaming, facilitating real-time interactions in persistent game environments.
00:09:06.720 The introduction of the first internet web browsers in the 1990s marked a pivotal evolution in digital communication, building upon the foundation established by bulletin board systems. As browsers gained popularity, they revolutionized how people accessed and interacted with information. The first web browsers introduced a graphical user interface that transformed the text-heavy format of bulletin board systems into visually engaging and user-friendly platforms. This innovation made the web more accessible and appealing to a broader audience, driving widespread adoption. This period marked a significant shift from niche community-focused bulletin boards to a globally connected World Wide Web, enabling new experiences and interactions on an unprecedented scale.
00:10:41.679 With the advent of the web browser, the digital landscape underwent a profound transformation. Earth 2025, created by Mayal Patel in 1996, emerged during this pivotal time as developers began to explore the possibilities of building dynamic web applications, moving beyond the geographic and visual constraints of bulletin board systems. Earth 2025 capitalized on emerging web server technology, providing players a browser-based strategy game that was accessible on a global scale.
00:11:20.000 Its release not only highlighted the expanding capabilities of web browsers but also demonstrated their potential to support complex interactive worlds, setting a new standard for what could be achieved in online gaming. However, the request-response model of early web technology lacked real-time interactivity. Due to the global nature of the internet, web browsers prioritized the efficient retrieval and display of information rather than the point-to-point connectivity provided by bulletin board systems. In some ways, this might have been considered a step backwards, but technology drives change, good and bad, whether we want it or not.
00:12:41.000 From the real-time interactivity of geographically local bulletin board systems to the globally accessible World Wide Web, technology reshapes the boundaries of what is possible. In December 2005, the release of Ruby on Rails 1 marked a significant milestone in web development history, introducing a powerful and efficient framework designed to streamline the creation of web applications. Its philosophy of convention over configuration and "don't repeat yourself" significantly simplified the development process, allowing developers to create applications with a standard structure and minimal boilerplate code.
00:14:14.520 These innovations greatly reduced development risk and increased productivity. As a result, Ruby on Rails quickly became a pivotal tool for startups and established companies alike, catalyzing the development of numerous high-profile websites and setting a new standard for web application design.
00:14:32.240 Rails is built on Rack, a Ruby web server interface that follows the request-response model defined by the HTTP standards. Rack itself follows the Common Gateway Interface (CGI) standard, which defines the structure of requests and responses, specifying that the client must send a request in order to receive a response from the server.
00:14:40.800 In other words, there is no way for the server to send data without the client requesting it. The request-response model, for all of its advantages, lacks real-time interactivity. As web technology evolved, user expectations for interactive and responsive interfaces grew, and the limitations of the current model became more apparent.
00:15:08.160 This led to the integration of features like Action Cable in later versions of Rails, which facilitate real-time communication via WebSockets, allowing Rails to meet modern web application demands while retaining its foundational principles. However, Ruby itself was not originally designed to support large numbers of diverse concurrent operations.
00:15:40.240 This inherent limitation of the language impacted the design and effectiveness of Action Cable. As a result, while Action Cable provides a framework for real-time communication within Rails applications, its capacity to handle multiple simultaneous real-time connections is effectively inherently limited by Ruby's architectural choices. This necessitates careful consideration and potentially supplementary solutions when scaling real-time applications built on Rails and Action Cable.
00:16:44.919 Despite the limitations of the request-response model, BrowserQuest was launched in 2012 as a joint effort between Little Workshop and Mozilla. This project demonstrated the advanced capabilities of modern web browsers, particularly their support for massively multiplayer real-time gaming. BrowserQuest leverages HTML5 for its rich media support and graphical capabilities while utilizing WebSockets for real-time bi-directional communication, allowing the browser and web server to exchange game updates and user input as they occurred.
00:17:37.919 This combination enables players to explore a vast world filled with adventures, friends, enemies, and treasure, all within their web browser without the need for plugins or additional software. This level of real-time multiplayer interaction was unprecedented in browser games at the time of its release. BrowserQuest not only demonstrated the practical application of cutting-edge web technologies but also inspired a wave of developers to explore new possibilities in online gaming.
00:18:27.400 Its success highlighted the potential for web applications to build complex, interactive environments. But alas, BrowserQuest also required a server designed to manage thousands of interactive sessions. Consequently, it was built on Node.js, renowned for its scalability, capable of handling numerous low-latency network connections, making it an ideal choice for real-time applications like BrowserQuest.
00:19:11.239 In fact, such a design simply would not have been feasible to build on Ruby and Rails at that time. In 2017, I created a Ruby gem called Async, which provides an interface for non-blocking, event-driven IO using fibers as the basic unit of concurrency.
00:19:36.800 At that time, I had been experimenting with creating my own DNS server in pure Ruby, but when I used it on my local network, everything stopped working. My original request handling code looked a little bit like this: first, we read a request, then we process the request and write the response. Seems simple enough, right? Well, if all your requests can be processed quickly, this approach is usually okay. But unfortunately, it's hiding a big problem. If you get a slow request, the loop will get stuck processing that request and all your other requests will be blocked. In the worst case, your system may get overloaded and experience a complete failure, like I did with my DNS server.
00:20:41.800 So how does the async gem for Ruby solve this problem? Well, in essence, we change this code and introduce an async block around the operation we want to execute concurrently. By doing this, we won't block the incoming request loop from handling new requests while we are generating responses. Now we have a slow request; we move it into its own async fiber, which can execute concurrently with other fibers, allowing us to handle other requests without blocking them.
00:21:43.000 Ruby's toolbox for parallelism and concurrency is growing with every release. So why would you use Async? Well, most importantly, it has an awesome logo created by Marina Louson. I hope you all get the joke by now. But seriously, why not use threads or M:N threading? It's easy to frame these technologies as competition, and as the creators of these technologies, we usually have a strong desire to see them become successful. But I like to think of it as more of a collaboration. We are all working together to make Ruby better, and we should try to take advantage of every opportunity. And while there are good discussions to be had about trade-offs, including being realistic about what works today and what's feasible in the future, it's also true that the execution model is only one part of the problem.
00:23:06.760 Async is about so much more than just fiber-based concurrency. At its core, Async leverages the IO event gem, which provides a high-performance event loop implemented specifically for Ruby, using the best available interfaces provided by your operating system. An event loop is a programming construct that continuously checks for and processes events, such as the availability of data from the network or user input, enabling non-blocking asynchronous execution of code.
00:23:22.200 This allows programs to handle multiple operations concurrently without waiting for each task to complete before starting the next one. The async scheduler, with the help of the IO event gem, implements the necessary interfaces for the Ruby fiber scheduler. This integration allows the scheduler to transform blocking operations within your program into internally non-blocking ones.
00:23:55.679 An internally non-blocking operation is one that appears to run normally but may internally context switch to the event loop, allowing other tasks to run concurrently. This is in contrast to more explicit mechanisms like the async await keywords, which force you to rewrite your code to take advantage of concurrency. Many operations are redirected to the fiber scheduler, including timeouts, IO reads and writes, waiting on child processes, joining threads, locking mutexes, popping from queues, and resolving addresses.
00:24:38.520 All of these operations and more become internally non-blocking when running within Async. As part of the IO hooks for reading and writing, we also built the IO buffer implementation for Ruby, which provides an interface for efficient memory-mapped IO, transferring data directly between memory and storage or network buffers to avoid redundant copies that can impact performance. All of this is built on top of native coroutines, which provide the best possible performance for context switching between tasks. We've hand-written the assembly implementations for all key architectures, including amd64, arm 32, arm 64, Windows, and x86.
00:25:40.159 These co-routines also form the basis of the M:N thread scheduler. In addition to this core set of functionality, we have extended Ruby to make it easier to deal with nonlinear programs. One such area is handling per-request state. There's been a lot of confusion in the past about how to best solve this problem, including a mixture of thread and fiber locals, so we introduced fiber storage, which provides a robust interface for per-request state while taking into account the various non-linear program flows such as fan-out, map-reduce, and worker pools.
00:26:15.360 Another crucial area is task cancellation, both of which are absolutely critical to building robust concurrent programs. We provide fiber raise and fiber kill to assist with managing task lifecycles, and these are used by the scheduler to propagate errors and cancellations throughout your program. We also introduced IO timeout, which provides a safety net for network IO. Before this feature, it was extremely tricky to implement robust IO timeouts, which is especially problematic when dealing with untrusted input.
00:27:25.440 Async is built on all of these components and more. Today, we are talking about Falcon, a scalable web server for Ruby built on top of Async. The main implementation of the server comes from a gem called async-http, which itself is built on top of several other gems that define the HTTP protocol, including HTTP/1, HTTP/2, and WebSockets.
00:28:03.920 Service configuration and execution is handled by the async service gem, which provides a standard way to configure one or more services for deployment. Services themselves execute inside a container provided by the async container gem, which offers both multi-process and multi-threaded execution models, along with rolling restarts, blue-green deployment, and system de-integration.
00:28:21.760 This allows Falcon to take advantage of multiple processor cores with zero downtime deployments. In the future, we may be able to provide multi-rector support too, and alongside that, we have adapters for Rack, which is the standard interface for web servers, allowing you to run any Rack-compatible web application on any Rack-compatible server.
00:29:38.640 One of the most important frameworks in the Ruby ecosystem is Rails. There have been many changes to Rails in order to fully support Falcon; however, some critical ones are still in the pipeline. Rails 7.1 now supports streaming responses as defined by the Rack 3 spec. Rails 7.1 also introduced support for request-per-fiber, which was specifically needed for fixing the Action Record connection pool connection handling. Rails 7.2 introduces further improvements...
00:30:53.920 to Active Record, including more efficient per-query connection handling. Finally, we plan to adapt Action Cable, which will allow us to swap out the default HTTP/1.1-only server included with Rails with a high-performance one implemented in Falcon. Additionally, I've created a small framework called Live, which provides interactive rendering of content updated via WebSockets. If you are familiar with Phoenix LiveView, it is very similar.
00:32:25.440 And finally, there is your application, which sits on top of all of this. Today, we are going to talk about Falcon and how to use it with Rails and Live to build real-time interactivity.
00:33:26.840 So who remembers this character? Excellent! This game called Flappy Bird was originally created by Dong Nguyen in 2013 and there have been many replicas created since. In this game, you must press the space bar to fly upwards. Gravity will push you down, and you must fly between the pipes. In fact, now that I think about it, this game doesn’t really make sense.
00:34:07.780 What if I told you this version of Flappy Bird is running in a web browser at 60 frames per second, responding to the user's input in real-time? Would that be surprising? What if I told you it was actually a Rails application, something that any of you could build today using Falcon? Would you believe me?
00:34:28.800 Well let me show you from start to finish how to build this game, how to implement real-time interactivity with Falcon, Rails, and Live. The first thing we need to do is create a new Rails application. After doing that, let's fetch our game assets. There are several images used by the game: the Flappy Bird, the background, and the pipe.
00:35:32.799 The first three assets will be rendered together like this. We need to download these three images into the app/assets/images directory; they are available from the complete examples Git repository, so let's fetch them using `wget`.
00:35:54.320 Okay, let's update the application's gems. First, we will remove Puma because we're going to replace it with Falcon. Now let's add Falcon, Live, and the console adapter Rails gem. Falcon is a web server, as we have discussed already, and Live provides a framework for real-time client-server interactivity. The console adapter Rails gem improves the default Rails logging so we can see what's going on more easily.
00:36:41.640 Now let's add the controller for our game. We will use a Rails generator to create our game controller with two actions: index and live. We will also skip the default routes, as the routes we will want to add are completely different, so let's go and edit the routes file. This is the default routes.rb file; we need to add routes for our game controller.
00:37:31.639 First, let's add the route for our root. We're going to route the root to the game controller's index action. We also need to add a route for the Live gem's WebSocket connection: for HTTP/1 it uses the `get` method, and for HTTP/2 it uses the `connect` method. Now, if you've done everything correctly so far, when you start the `bin/rails server`, you should get a page like this.
00:38:17.320 Okay, now we need to add some JavaScript dependencies. Before we do that, let me briefly explain why we have JavaScript dependencies on the server side. We have the Live gem, which is already installed, and on the client side, we have Live.js, which is what we're about to add. Together, they communicate using a WebSocket so that the server can send updates to the client and the client can send events to the server.
00:39:26.720 Together, these two components work to provide a framework for real-time interactivity between the web browser and the application server. We will use import maps in Rails to fetch the dependencies. Simply run `bin/importmap pin socketry live` to pull in both the Live.js code itself as well as MorphDOM, which Live.js uses for updating the client side DOM. We also need to set up the Live.js integration with our application.
00:40:24.500 So we need to edit app/javascript/application.js. First, we need to import the Live class, and then we need to start the connection handling code and assign it to a global within the browser window. Now let's put together a very simple skeleton implementation of the application. First, we will create a class in lib called FlappyView. This implementation will simply render the text 'Flappy View' into a div.
00:41:06.560 Now let's modify our Rails game controller. First, we need to introduce a resolver; when we render a live view in the client browser, we expect it to bind to an instance of that view class on the server. A resolver is a security mechanism that limits what classes can be instantiated on the server, ensuring that only safe, intended classes are allowed.
00:41:45.520 Next, in the index method, we should create an instance of the Flappy View so we can render it in the initial page load. This allows live views to be used for progressive enhancement. The initial response HTML contains a static copy of the view, which can then be subsequently updated by the WebSocket connection. Once the WebSocket connection is established and the view binds to the server-side instance, we need to handle the incoming WebSocket connection using the Rails adapter.
00:42:37.240 Since this WebSocket connection doesn't include CSRF tokens, we need to skip the CSRF validation in the live action. We wrap the incoming request using a WebSocket and pass that connection to an instance of LivePage, which handles all subsequent interactions between the client and server views. I'd like to point out that it was a huge achievement to make WebSockets work in Rails like this. Finally, let's update the index view so that it renders the initial tag. This will generate the initial static HTML as per the view render method.
00:43:39.560 If you've done everything correctly, when you start the bin rails server, you should get a page like this. If we inspect the Flappy View, we can see that the HTML tag representing the Flappy View was rendered with the live class, which indicates the element is interactive, and the data class attribute tells us which class to instantiate on the server side. We can also see a log message which indicates the client-server view has been bound to the server-side instance of the Flappy View class. Now there is an active WebSocket connection which can be used to exchange events and updates.
00:44:37.960 Okay, now it's time to make it look pretty. Firstly, let's center the Flappy View both horizontally and vertically in the browser window. I know centering something with CSS. What kind of magic is that? Now, here is the main CSS selector for the Flappy div tag. We will set the background image, the size, and position it so that we have a well-defined origin. We also set the overflow to hidden, which is especially important for the pipes that will extend above and below the view.
00:45:46.479 Let's add the selectors for the Flappy Bird sprite itself. We will use a z-index to control the depth and use absolute positioning relative to the parent origin. We will apply the same styles to the pipe but increase the z-index so that the bird will be rendered behind them in case of a collision. Once this is done correctly, our current Flappy View will show the background image, and it's starting to come together.
00:46:20.680 Now it is time to add the Flappy Bird logic. Let's talk about physics. We have gravity, which is 9.8 m/s², acting downwards on the bird. We have the flapping force, which we will model as an instantaneous upward velocity of 6 m/s. It's not physically accurate, but it felt reasonable in playtesting. In our game, 50 pixels per meter seem to give good physics response, so that means we need to scale our units accordingly.
00:47:12.880 We also have a bounding box for the bird, with the origin at the lower left and a height of 24 pixels, giving our bird a real-world size of about half a meter tall. Sounds realistic enough. So let's implement that. We're going to completely rewrite our Flappy View code. First, we'll define our physics constants: the width and height of the view, the scaled gravity force, and the scaled flapping force.
00:48:15.760 Next, we'll define a bounding box class that can handle our 2D shapes. It has an origin X and Y and a size defined by width and height. We provide some convenient attributes and methods for accessing the coordinates, including right, which is the origin plus the width, and top, which is the origin plus the height. Together, these form an axis-aligned bounding box, which we can use for 2D animations and collision detection.
00:49:06.000 We implement intersection using separating planes test: in short, we check if we're to the left of, right of, below, or above the object we're intersecting with. If none of those are true, we could not find a separating plane, and therefore the only remaining option is that we must be intersecting with it.
00:49:51.040 Now that we have a bounding box, we can use it to define our bird subclass. We use a single instance variable, velocity, to model the vertical motion. The step method takes a delta time argument and applies the physics we discussed earlier. Gravity, as a downward force, is applied, and then the position is updated according to the velocity. We also clamp the y-position to the height of the view so that the bird cannot fly off the top.
00:50:43.040 For user input, we have a method called flap, which applies the instantaneous upwards force. Finally, we define a method called render, which generates a div tag with the appropriate values for left, bottom, width, and height.
00:51:52.040 Okay, now it's time to implement our game logic. Firstly, we will initialize our view, setting everything to nil. Next, we will introduce a reset method which resets the game state by replacing the bird state and starting the game event loop by calling the run method that we will define in a moment.
00:52:12.280 We also need to define a step method that takes a delta time and updates the whole game state according to the physics equations we defined earlier. If the bird falls off the bottom of the view, we consider it game over and reset the state.
00:53:05.640 Now let’s implement the game loop, a critical part of any real-time and directive view. We use an asynchronous task which runs at a specified frame rate, in this case, 60 FPS, and updates the physics at a fixed time step. We then trigger the update method that redraws the client view by sending updated HTML over the WebSocket connection. Finally, we sleep for a specified time step to keep the loop running at the right frame rate.
00:53:40.560 Now we need to define a few critical lifecycle methods. The bind method is invoked when a client connects or binds, connecting a client-side view to the server-side instance. In this case, we reset the game state. The close method is invoked when a client disconnects and we no longer have an active connection; in this case, we stop the game loop. These events can be triggered not only by opening and closing a browser window, but also by window visibility; if you switch tabs, it will disconnect and reconnect when you come back.
00:54:35.240 Now let’s handle the user input. When the user presses the space bar in the web browser, it generates a key press event and we should invoke the flap method. This Ruby method handles when an event from the client-side view is forwarded to the server. To forward these events, which include which button was pressed, we also want to define a method called forwardKeyPress, which generates a small JavaScript event handler.
00:55:20.710 In the render method, since we want to receive input on a div tag that doesn't normally generate key presses, we need to specify a tab index along with our forwardKeyPress event handler. We also render all child elements, in this case, the bird. If everything is working correctly, when you run your program again, you should get something like this, and pressing the space bar should cause the bird to go up.
00:56:06.960 If the bird falls off the bottom of the screen, the game resets. Okay, so the final piece of this game is the pipe logic. The Flappy Bird has to fly between the pipes. The simplest way for us to simulate pipes is to move them to the left at a fixed velocity.
00:56:27.040 We model the pipes using an origin in the middle of the gap and an offset which is the distance to the pipe above and below. A smaller offset means a smaller gap. Using these numbers, we can easily compute bounding boxes for the upper and lower segments. The bounding box of the bird and the bounding box of the pipe segment can be used to compute intersections; if the bird's bounding box intersects the pipe's bounding box, the game should reset as we move the pipes to the left.
00:57:04.240 We also have a reset mechanism which moves them back to the right side. This repeats over and over again, giving the illusion of a continuous stream of pipes.
00:57:32.000 So let us now implement our pipe class. Similarly to the bird, we define our coordinates which include the origin X and Y and the offset which is the distance from the origin to the pipe segments, and the size within height of the sprite. We also introduce some helpful computed coordinates including the right side, which is the origin plus the width; the top gap is the origin plus the offset, and the bottom of the segment is the origin minus the offset minus the sprite height.
00:58:38.240 Now let's define the reset logic that moves the pipes back to the right hand side. We introduce a method called scaledRandom, which generates random small permutations in the position of the pipe to make the game a little more different. We then introduce a reset method that moves the pipe back past the right side of the view and adjusts its vertical position using the scaledRandom value. Finally, we implement our physics step which moves the pipe to the left according to the pipe velocity.
00:59:24.080 When the pipe moves completely off the left side of the view, we reset it, moving it back to the right, ready to start moving to the left again.
00:59:38.400 Now let's implement collision detection. We define an upper and lower bounding box corresponding to the two pipe segments. We then use these two bounding boxes to check if there was an intersection with some other bounding box. We will use this method in our main game step to determine if the bird has crashed into the pipe. Finally, we need to render our two pipe segments. This is fairly straightforward; we just need to generate two div tags with the appropriate CSS classes, positions, and sizes.
01:00:31.680 Okay, now we have our pipe logic implemented. We need to update the game logic to add the pipes. First, we will introduce a new instance variable for storing the pipes and initialize it to nil. We will also modify our reset method.
01:00:49.880 During reset, we will initialize two sets of pipes: one in the middle of the game view and one off to the right. We need to update our game step method too; for each pipe, we will step through its own logic so that it moves to the left. Then we will check for any intersection with the bird, and if so, we will reset the game.
01:01:07.920 Finally, we have to update our render method. We need to render any pipes we have in the game world.
01:01:27.720 Once you have done this, you'll have a working Flappy Bird game with user input, moving pipes, and collision detection operating in real-time within a Rails application on top of Falcon. All the source code for this example is available here, so please do try it out for yourself and enjoy Flappy Bird.
01:01:39.440 And I would like to share the first demonstration of Flappy Bird...
01:01:45.640 - is Matt here?
01:01:57.919 Come on! You got to try!