plezi 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/CHANGELOG.md +450 -0
  4. data/Gemfile +4 -0
  5. data/KNOWN_ISSUES.md +13 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +341 -0
  8. data/Rakefile +2 -0
  9. data/TODO.md +19 -0
  10. data/bin/plezi +301 -0
  11. data/lib/plezi.rb +125 -0
  12. data/lib/plezi/base/cache.rb +77 -0
  13. data/lib/plezi/base/connections.rb +33 -0
  14. data/lib/plezi/base/dsl.rb +177 -0
  15. data/lib/plezi/base/engine.rb +85 -0
  16. data/lib/plezi/base/events.rb +84 -0
  17. data/lib/plezi/base/io_reactor.rb +41 -0
  18. data/lib/plezi/base/logging.rb +62 -0
  19. data/lib/plezi/base/rack_app.rb +89 -0
  20. data/lib/plezi/base/services.rb +57 -0
  21. data/lib/plezi/base/timers.rb +71 -0
  22. data/lib/plezi/handlers/controller_magic.rb +383 -0
  23. data/lib/plezi/handlers/http_echo.rb +27 -0
  24. data/lib/plezi/handlers/http_host.rb +215 -0
  25. data/lib/plezi/handlers/http_router.rb +69 -0
  26. data/lib/plezi/handlers/magic_helpers.rb +43 -0
  27. data/lib/plezi/handlers/route.rb +272 -0
  28. data/lib/plezi/handlers/stubs.rb +143 -0
  29. data/lib/plezi/server/README.md +33 -0
  30. data/lib/plezi/server/helpers/http.rb +169 -0
  31. data/lib/plezi/server/helpers/mime_types.rb +999 -0
  32. data/lib/plezi/server/protocols/http_protocol.rb +318 -0
  33. data/lib/plezi/server/protocols/http_request.rb +133 -0
  34. data/lib/plezi/server/protocols/http_response.rb +294 -0
  35. data/lib/plezi/server/protocols/websocket.rb +208 -0
  36. data/lib/plezi/server/protocols/ws_response.rb +92 -0
  37. data/lib/plezi/server/services/basic_service.rb +224 -0
  38. data/lib/plezi/server/services/no_service.rb +196 -0
  39. data/lib/plezi/server/services/ssl_service.rb +193 -0
  40. data/lib/plezi/version.rb +3 -0
  41. data/plezi.gemspec +26 -0
  42. data/resources/404.erb +68 -0
  43. data/resources/404.haml +64 -0
  44. data/resources/404.html +67 -0
  45. data/resources/404.slim +63 -0
  46. data/resources/500.erb +68 -0
  47. data/resources/500.haml +63 -0
  48. data/resources/500.html +67 -0
  49. data/resources/500.slim +63 -0
  50. data/resources/Gemfile +85 -0
  51. data/resources/anorexic_gray.png +0 -0
  52. data/resources/anorexic_websockets.html +47 -0
  53. data/resources/code.rb +8 -0
  54. data/resources/config.ru +39 -0
  55. data/resources/controller.rb +139 -0
  56. data/resources/db_ac_config.rb +58 -0
  57. data/resources/db_dm_config.rb +51 -0
  58. data/resources/db_sequel_config.rb +42 -0
  59. data/resources/en.yml +204 -0
  60. data/resources/environment.rb +41 -0
  61. data/resources/haml_config.rb +6 -0
  62. data/resources/i18n_config.rb +14 -0
  63. data/resources/rakefile.rb +22 -0
  64. data/resources/redis_config.rb +35 -0
  65. data/resources/routes.rb +26 -0
  66. data/resources/welcome_page.html +72 -0
  67. data/websocket chatroom.md +639 -0
  68. metadata +141 -0
@@ -0,0 +1,125 @@
1
+ ### Ruby core extentions
2
+
3
+ require 'singleton'
4
+ require 'pathname'
5
+ require 'logger'
6
+ require 'socket'
7
+ require 'openssl'
8
+ require 'strscan'
9
+ require 'base64'
10
+ require 'digest/sha1'
11
+ require 'securerandom'
12
+ require 'time'
13
+ require 'json'
14
+
15
+ ### version
16
+
17
+ require "plezi/version"
18
+
19
+
20
+ ### Server requirements
21
+
22
+ require "plezi/server/services/basic_service"
23
+ require "plezi/server/services/ssl_service"
24
+ require "plezi/server/services/no_service"
25
+
26
+ require "plezi/server/protocols/http_protocol"
27
+ require 'plezi/server/protocols/http_request'
28
+ require 'plezi/server/protocols/http_response'
29
+
30
+ require "plezi/server/helpers/http"
31
+ require "plezi/server/helpers/mime_types"
32
+
33
+ require "plezi/server/protocols/websocket"
34
+ require 'plezi/server/protocols/ws_response'
35
+
36
+ ## Server-Framework Bridges
37
+ require "plezi/handlers/http_echo"
38
+ require "plezi/handlers/http_host"
39
+ require "plezi/handlers/http_router"
40
+
41
+ require "plezi/handlers/controller_magic"
42
+ require "plezi/handlers/magic_helpers"
43
+ require "plezi/handlers/route"
44
+
45
+ require "plezi/handlers/stubs"
46
+
47
+ ### Framework requirements
48
+ require "plezi/base/events"
49
+ require "plezi/base/timers"
50
+ require "plezi/base/services"
51
+ require "plezi/base/connections"
52
+ require "plezi/base/logging"
53
+ require "plezi/base/io_reactor"
54
+ require "plezi/base/cache"
55
+ require "plezi/base/engine"
56
+
57
+ ### DSL requirements
58
+ require "plezi/base/dsl"
59
+
60
+ ### optional Rack
61
+ require "plezi/base/rack_app"
62
+
63
+ ## erb templating
64
+ begin
65
+ require 'erb'
66
+ rescue Exception => e
67
+
68
+ end
69
+
70
+
71
+
72
+ ##############################################################################
73
+ # To make something new, we leap to the unknown.
74
+ ##############################################################################
75
+ # Plezi is a stand alone web services app, which supports RESTful HTTP, HTTP Streaming and WebSockets.
76
+ #
77
+ # Plezi is a wonderful alternative to Socket.io which makes writing the server using Ruby a breeze.
78
+ #
79
+ # Plezi routes accept Regexp's (regular exceptions) for route paths. for example:
80
+ #
81
+ # require 'plezi'
82
+ # listen
83
+ # route(/[.]*/) {|request, response| response << "Your request, master: #{request.path}."}
84
+ #
85
+ # The catch-all route (/[.]*/) has a shortcut '*', so it's possible to write:
86
+ #
87
+ # require 'plezi'
88
+ # listen
89
+ # route('*') {|request, response| response << "Your request, master: #{request.path}."}
90
+ #
91
+ #
92
+ # Plezi accepts an optional class object that can be passed using the `route` command. Passing a class object is especially useful for RESTful and WebSocket applications.
93
+ # read more at the Plezi::StubWSCtrl and Plezi::StubRESTCtrl documentation, which are stub classes used for testing routes.
94
+ #
95
+ # require 'plezi'
96
+ # listen
97
+ # route "*", Plezi::StubRESTCtrl
98
+ #
99
+ # class routes that have a specific path (including root, but not a catch-all or Regexp path)
100
+ # accept an implied `params[:id]` variable. the following path ('/'):
101
+ #
102
+ # require 'plezi'
103
+ # listen
104
+ # route "/", Plezi::StubRESTCtrl
105
+ # # client requests: /1
106
+ # # => Plezi::StubRESTCtrl.new.show() # where params[:id] == 1
107
+ #
108
+ # it is possible to use "magic" routes (i.e. `/resource/:type/(:id)/(:date){/[0-9]{8}}/:foo`) and it is also possible to set the appropriate paramaters within the `before` method of the Conltroller.
109
+ #
110
+ # Routes are handled in the order they are created. If overlapping routes exist, the first will execute first:
111
+ #
112
+ # require 'plezi'
113
+ # listen
114
+ # route('*') do |request, response|
115
+ # response << "Your request, master: #{request.path}." unless request.path.match /cats/
116
+ # end
117
+ # route('*') {|request, response| response.body << "Ahhh... I love cats!"}
118
+ #
119
+ # all the examples above shuold be good to run from irb. updated examples can be found at the Readme file in the Github project: https://github.com/boazsegev/plezi
120
+ #
121
+ # thanks to Russ Olsen for his ideas for a DSL and his blog post at:
122
+ # http://www.jroller.com/rolsen/entry/building_a_dsl_in_ruby1
123
+ ##############################################################################
124
+ module Plezi
125
+ end
@@ -0,0 +1,77 @@
1
+
2
+ module Plezi
3
+
4
+ # File and Object Caching for Plezi
5
+ module_function
6
+ # contains the cached data, in the format: CACHE_STORE["filename"] = CacheObject
7
+ CACHE_STORE = {}
8
+ LOCK = Mutex.new
9
+ CACHABLE = %w{cache object slim haml css map js html scss sass coffee txt xml json yaml rb}
10
+
11
+ @cache_to_disk = true
12
+
13
+ # this class holds cached objects (data and modification times)
14
+ class CacheObject
15
+ # Cached attributes
16
+ attr_accessor :data, :mtime
17
+
18
+ # initialize a Cached object
19
+ def initialize d = nil, t = Time.now
20
+ @data, @mtime = d, t
21
+ end
22
+ end
23
+
24
+ # load the file from the cache (if exists) or the file system (if it doesn't)
25
+ def load_file filename
26
+ cached?(filename) ? get_cached(filename) : reload_file(filename)
27
+ end
28
+ # review a file's modification time
29
+ def file_mtime filename
30
+ return CACHE_STORE[filename].mtime if cached?(filename)
31
+ File.mtime(filename)
32
+ end
33
+
34
+ # force a file onto the cache (only if it is cachable - otherwise will load the file but will not cache it).
35
+ def reload_file filename
36
+ if CACHABLE.include? filename.match(/\.([^\.]+)$/)[1]
37
+ return cache_data filename, IO.read(filename), File.mtime(filename)
38
+ else
39
+ return IO.read(filename)
40
+ end
41
+ end
42
+ # places data into the cache, and attempts to save the data to a file name.
43
+ def save_file filename, data, save_to_disk = false
44
+ cache_data filename, data if CACHABLE.include? filename.match(/\.([^\.]+)$/)[1]
45
+ begin
46
+ IO.write filename, data if save_to_disk
47
+ rescue Exception => e
48
+ Plezi.warn("File couldn't be written (#{filename}) - file system error?")
49
+ end
50
+ data
51
+ end
52
+ # places data into the cache, under an identifier ( file name ).
53
+ def cache_data filename, data, mtime = Time.now
54
+ LOCK.synchronize { CACHE_STORE[filename] = CacheObject.new( data, mtime ) }
55
+ data
56
+ end
57
+ # Get data from the cache. will throw an exception if there is no data in the cache.
58
+ def get_cached filename
59
+ CACHE_STORE[filename].data # if CACHE_STORE[filename]
60
+ end
61
+
62
+ # returns true if the filename is cached.
63
+ def cached? filename
64
+ !CACHE_STORE[filename].nil?
65
+ end
66
+
67
+ # returns true if the file exists on disk or in the cache.
68
+ def file_exists? filename
69
+ (CACHE_STORE[filename] || File.exists?(filename)) ? true : false
70
+ end
71
+
72
+ # returns true if the file has been update since data was last cached.
73
+ def cache_needs_update? filename
74
+ return true if CACHE_STORE[filename].nil? || CACHE_STORE[filename].mtime < File.mtime(filename)
75
+ false
76
+ end
77
+ end
@@ -0,0 +1,33 @@
1
+
2
+ module Plezi
3
+
4
+ module_function
5
+
6
+ # DANGER ZONE - Plezi Engine. the connections store
7
+ IO_CONNECTION_DIC = {}
8
+ # DANGER ZONE - Plezi Engine. the connections mutex
9
+ C_LOCKER = Mutex.new
10
+
11
+ # Plezi Engine, DO NOT CALL. disconnectes all active connections
12
+ def stop_connections
13
+ log 'Stopping connections'
14
+ C_LOCKER.synchronize {IO_CONNECTION_DIC.values.each {|c| c.timeout = -1; callback c, :on_disconnect unless c.disconnected?} ; IO_CONNECTION_DIC.clear}
15
+ end
16
+
17
+ # Plezi Engine, DO NOT CALL. adds a new connection to the connection stack
18
+ def add_connection io, params
19
+ connection = params[:service_type].new(io, params)
20
+ C_LOCKER.synchronize {IO_CONNECTION_DIC[connection.socket] = connection} if connection
21
+ callback(connection, :on_message)
22
+ end
23
+ # Plezi Engine, DO NOT CALL. removes a connection from the connection stack
24
+ def remove_connection connection
25
+ C_LOCKER.synchronize { IO_CONNECTION_DIC.delete connection.socket }
26
+ end
27
+
28
+ # clears closed connections from the stack
29
+ def clear_connections
30
+ C_LOCKER.synchronize { IO_CONNECTION_DIC.values.each {|c| callback c, :on_disconnect if c.disconnected? || c.timedout? } }
31
+ end
32
+
33
+ end
@@ -0,0 +1,177 @@
1
+
2
+ module Plezi
3
+
4
+ # this module contains the methods that are used as a DSL and sets up easy access to the Plezi framework.
5
+ #
6
+ # use the`listen`, `host` and `route` functions rather then accessing this object.
7
+ #
8
+ module DSL
9
+ module_function
10
+
11
+ @servers = {}
12
+ @active_router = nil
13
+
14
+ # adds a server (performs the action required by the listen method).
15
+ #
16
+ # accepts:
17
+ # port:: (optional) the port number for the service. if not defined, defaultes to the -p runtime argument (i.e. `./app.rb -p 8080`) or to 3000 if argument is missing.
18
+ # params:: any parameter accepted by the Plezi.add_service method. defaults to: `:protocol=>Plezi::HTTPProtocol, :handler => HTTPRouter.new`
19
+ def listen(port, params = {})
20
+ # set port and arguments
21
+ if port.is_a?(Hash)
22
+ params = port
23
+ port = nil
24
+ end
25
+ if !port && defined? ARGV
26
+ if ARGV.find_index('-p')
27
+ port_index = ARGV.find_index('-p') + 1
28
+ port ||= ARGV[port_index].to_i
29
+ ARGV[port_index] = (port + 1).to_s
30
+ else
31
+ ARGV << '-p'
32
+ ARGV << '3000'
33
+ return listen nil, params
34
+ end
35
+ end
36
+ port ||= 3000
37
+
38
+ # create new service or choose existing
39
+ if @servers[port]
40
+ puts "WARNING: port aleady in use! returning existing service and attemptin to add host (maybe multiple hosts? use `host` instead)." unless params[:host]
41
+ @active_router = @servers[port][:handler]
42
+ @active_router.add_host params[:host], params
43
+ return @active_router
44
+ end
45
+ params[:protocol] ||= HTTPProtocol
46
+ @active_router = params[:handler] ||= HTTPRouter.new # HTTPEcho #
47
+ @active_router.add_host params[:host], params
48
+ return false unless Plezi.add_service(port, params)
49
+ @servers[port] = params
50
+ @active_router
51
+ end
52
+
53
+ # adds a route to the last server created
54
+ def route(path, controller = nil, &block)
55
+ @active_router.add_route path, controller, &block
56
+ end
57
+
58
+
59
+ # adds a shared route to all existing services and hosts.
60
+ def shared_route(path, controller = nil, &block)
61
+ @servers.values.each {|p| p[:handler].add_shared_route path, controller, &block }
62
+ end
63
+
64
+ # adds a host to the last server created
65
+ #
66
+ # accepts the same parameter(s) as the `listen` command (see Plezi.add_service), except :protocol and :handler are ignored:
67
+ # alias:: a String or an Array of Strings which represent alternative host names (i.e. `alias: ["admin.google.com", "admin.gmail.com"]`).
68
+ def host(host_name, params)
69
+ @active_router.add_host host_name, params
70
+ end
71
+ end
72
+ end
73
+
74
+ Encoding.default_internal = 'utf-8'
75
+ Encoding.default_external = 'utf-8'
76
+
77
+ # Set a shortcut for the Plezi module.
78
+ PL = Plezi
79
+
80
+ # creates a server object and waits for routes to be set.
81
+ #
82
+ # port:: the port to listen to. the first port defaults to 3000 and increments by 1 with every `listen` call. it's possible to set the first port number by running the app with the -p paramater.
83
+ # params:: a Hash of serever paramaters, as listed in the Plezi#add_service documentation.
84
+ #
85
+ # The different keys in the params hash control the server's behaviour, as follows:
86
+ #
87
+ # host:: the host name. defaults to any host not explicitly defined (a catch-all).
88
+ # alias:: a String or an Array of Strings which represent alternative host names (i.e. `alias: ["admin.google.com", "admin.gmail.com"]`).
89
+ # root:: the public root folder. if this is defined, static files will be served from the location.
90
+ # assets:: the assets root folder. defaults to nil (no assets support). if the path is defined, assets will be served from `/assets/...` (or the public_asset path defined) before any static files. assets will not be served if the file in the /public/assets folder if up to date (a rendering attempt will be made for systems that allow file writing).
91
+ # assets_public:: the assets public uri location (uri format, NOT a file path). defaults to `/assets`. assets will be saved (or rendered) to the assets public folder and served as static files.
92
+ # assets_callback:: a method that accepts one parameters: `request` and renders any custom assets. the method should return `false` unless it has created a response object (`response = Plezi::HTTPResponse.new(request)`) and sent a response to the client using `response.finish`.
93
+ # save_assets:: saves the rendered assets to the filesystem, under the public folder. defaults to false.
94
+ # templates:: the templates root folder. defaults to nil (no template support). templates can be rendered by a Controller class, using the `render` method.
95
+ # ssl:: if true, an SSL service will be attempted. if no certificate is defined, an attempt will be made to create a self signed certificate.
96
+ # ssl_key:: the public key for the SSL service.
97
+ # ssl_cert:: the certificate for the SSL service.
98
+ #
99
+ def listen(port = nil, params = {})
100
+ Plezi::DSL.listen port, params
101
+ end
102
+
103
+ # adds a virtul host to the current service (the last `listen` call) or switches to an existing host within the active service.
104
+ #
105
+ # accepts:
106
+ # host_name: a String with the full host name (i.e. "www.google.com" / "mail.google.com")
107
+ # params:: any of the parameters accepted by the `listen` command, except `protocol`, `handler`, and `ssl` parameters.
108
+ def host(host_name = false, params = {})
109
+ Plezi::DSL.host host_name, params
110
+ end
111
+
112
+ # adds a route to the last server object
113
+ #
114
+ # path:: the path for the route
115
+ # controller:: The controller class which will accept the route.
116
+ #
117
+ # `path` paramaters has a few options:
118
+ #
119
+ # * `path` can be a Regexp object, forcing the all the logic into controller (typically using the before method).
120
+ #
121
+ # * simple String paths are assumed to be basic RESTful paths:
122
+ #
123
+ # route "/users", Controller => route "/users/(:id)", Controller
124
+ #
125
+ # * routes can define their own parameters, for their own logic:
126
+ #
127
+ # route "/path/:required_paramater/:required_paramater{with_format}/(:optional_paramater)/(:optional){with_format}"
128
+ #
129
+ # * routes can define optional or required routes with regular expressions in them:
130
+ #
131
+ # route "(:locale){en|ru}/path"
132
+ #
133
+ # * routes which use the special '/' charecter within a parameter's format, must escape this charecter using the '\' charecter. **Notice the single quotes** in the following example:
134
+ #
135
+ # route '(:math){[\d\+\-\*\^\%\.\/]}'
136
+ #
137
+ # * or, with double quotes:
138
+ #
139
+ # route "(:math){[\\d\\+\\-\\*\\^\\%\\.\\/]}"
140
+ #
141
+ # magic routes make for difficult debugging - the smarter the routes, the more difficult the debugging.
142
+ # use with care and avoid complex routes when possible. RESTful routes are recommended when possible.
143
+ # json serving apps are advised to use required paramaters, empty sections indicating missing required paramaters (i.e. /path///foo/bar///).
144
+ #
145
+ def route(path, controller = nil, &block)
146
+ Plezi::DSL.route(path, controller, &block)
147
+ end
148
+
149
+ # adds a route to the all the existing servers and hosts.
150
+ #
151
+ # accepts same options as route.
152
+ def shared_route(path, controller = nil, &block)
153
+ Plezi::DSL.shared_route(path, controller, &block)
154
+ end
155
+
156
+ # finishes setup of the servers and starts them up. This will hange the proceess.
157
+ #
158
+ # this method is called automatically by the Plezi framework.
159
+ #
160
+ # it is recommended that you DO NOT CALL this method.
161
+ # if any post shut-down actions need to be performed, use Plezi.on_shutdown instead.
162
+ def start_services
163
+ return 0 if ( defined?(NO_PLEZI_AUTO_START) || defined?(BUILDING_PLEZI_TEMPLATE) || defined?(PLEZI_ON_RACK) )
164
+ Object.const_set "NO_PLEZI_AUTO_START", true
165
+ undef listen
166
+ undef host
167
+ undef route
168
+ undef shared_route
169
+ undef start_services
170
+ Plezi.start_services
171
+ end
172
+
173
+ # sets to start the services once dsl script is finished loading.
174
+ at_exit { start_services } unless ( defined?(NO_PLEZI_AUTO_START) || defined?(BUILDING_PLEZI_TEMPLATE) || defined?(PLEZI_ON_RACK) )
175
+
176
+ # sets a name for the process (on some systems).
177
+ $0="Plezi (Ruby)"
@@ -0,0 +1,85 @@
1
+
2
+ module Plezi
3
+
4
+ module_function
5
+
6
+ # Plezi event cycle settings: gets how many worker threads Plezi will run.
7
+ def max_threads
8
+ @max_threads ||= 16
9
+ end
10
+ # Plezi event cycle settings: sets how many worker threads Plezi will run.
11
+ def max_threads= value
12
+ @max_threads = value
13
+ end
14
+
15
+ # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
16
+ #
17
+ # No timing methods will be called during this interval.
18
+ #
19
+ # get the current idle setting
20
+ def idle_sleep
21
+ @idle_sleep ||= 0.1
22
+ end
23
+ # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
24
+ #
25
+ # No timing methods will be called during this interval.
26
+ #
27
+ # set the current idle setting
28
+ def idle_sleep= value
29
+ @idle_sleep = value
30
+ end
31
+
32
+ # Plezi Engine, DO NOT CALL. creates the thread pool and starts cycling through the events.
33
+ def start_services
34
+ # prepare threads
35
+ exit_flag = false
36
+ threads = []
37
+ run_every(5 , Plezi.method(:clear_connections)) #{info "Cleared inactive Connections"}
38
+ run_every 3600 , GC.method(:start)
39
+ # run_every( 1 , Proc.new() { Plezi.info "#{IO_CONNECTION_DIC.length} active connections ( #{ IO_CONNECTION_DIC.select{|k,v| v.protocol.is_a?(WSProtocol)} .length } websockets)." })
40
+ (max_threads).times { Thread.new { thread_cycle until exit_flag } }
41
+
42
+ # Thread.new { check_connections until SERVICES.empty? }
43
+ #...
44
+ # set signal tarps
45
+ trap('INT'){ exit_flag = true; raise "close Plezi" }
46
+ trap('TERM'){ exit_flag = true; raise "close Plezi" }
47
+ puts 'Services running. Press ^C to stop'
48
+ # sleep until trap raises exception (cycling might cause the main thread to ignor signals and lose attention)
49
+ (sleep unless SERVICES.empty?) rescue true
50
+ # start shutdown.
51
+ exit_flag = true
52
+ # set new tarps
53
+ trap('INT'){ puts 'Forced exit.'; Kernel.exit }#rescue true}
54
+ trap('TERM'){ puts 'Forced exit.'; Kernel.exit }#rescue true }
55
+ puts 'Started shutdown process. Press ^C to force quit.'
56
+ # shut down listening sockets
57
+ stop_services
58
+ # disconnect active connections
59
+ stop_connections
60
+ # cycle down threads
61
+ info "Waiting for workers to cycle down"
62
+ threads.each {|t| t.join if t.alive?}
63
+
64
+ # rundown any active events
65
+ thread_cycle
66
+
67
+ # call shutdown callbacks
68
+ SHUTDOWN_CALLBACKS.each {|s| s[0].call(*s[1]) }
69
+ SHUTDOWN_CALLBACKS.clear
70
+
71
+ # return exit code?
72
+ 0
73
+ end
74
+
75
+ # Plezi Engine, DO NOT CALL. runs one thread cycle
76
+ def self.thread_cycle flag = 0
77
+ io_reactor rescue false # stop_connections
78
+ true while fire_event
79
+ fire_timers
80
+
81
+ rescue Exception => e
82
+
83
+ error e
84
+ end
85
+ end