cyberarm_engine 0.25.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -8
  3. data/.rubocop.yml +7 -7
  4. data/.travis.yml +5 -5
  5. data/Gemfile +6 -6
  6. data/Gemfile.lock +37 -25
  7. data/LICENSE.txt +21 -21
  8. data/README.md +74 -74
  9. data/Rakefile +10 -10
  10. data/assets/shaders/fragment/g_buffer.glsl +30 -30
  11. data/assets/shaders/fragment/lighting.glsl +115 -115
  12. data/assets/shaders/include/light_struct.glsl +11 -11
  13. data/assets/shaders/include/material_struct.glsl +16 -16
  14. data/assets/shaders/vertex/g_buffer.glsl +28 -28
  15. data/assets/shaders/vertex/lighting.glsl +24 -24
  16. data/assets/textures/ui/circle.png +0 -0
  17. data/assets/textures/ui/circle_small.png +0 -0
  18. data/assets/textures/ui/rounded.png +0 -0
  19. data/assets/textures/ui/rounded_bottom.png +0 -0
  20. data/assets/textures/ui/rounded_left.png +0 -0
  21. data/assets/textures/ui/rounded_left_bottom.png +0 -0
  22. data/assets/textures/ui/rounded_left_top.png +0 -0
  23. data/assets/textures/ui/rounded_right.png +0 -0
  24. data/assets/textures/ui/rounded_right_bottom.png +0 -0
  25. data/assets/textures/ui/rounded_right_top.png +0 -0
  26. data/assets/textures/ui/rounded_small.png +0 -0
  27. data/assets/textures/ui/rounded_tiny.png +0 -0
  28. data/assets/textures/ui/rounded_top.png +0 -0
  29. data/assets/textures/ui/square.png +0 -0
  30. data/bin/console +14 -14
  31. data/bin/setup +8 -8
  32. data/cyberarm_engine.gemspec +35 -36
  33. data/lib/cyberarm_engine/animator.rb +219 -219
  34. data/lib/cyberarm_engine/background.rb +156 -158
  35. data/lib/cyberarm_engine/background_image.rb +93 -93
  36. data/lib/cyberarm_engine/background_nine_slice.rb +182 -142
  37. data/lib/cyberarm_engine/bounding_box.rb +150 -150
  38. data/lib/cyberarm_engine/builtin/intro_state.rb +130 -130
  39. data/lib/cyberarm_engine/cache/download_manager.rb +123 -123
  40. data/lib/cyberarm_engine/cache.rb +4 -4
  41. data/lib/cyberarm_engine/common.rb +133 -131
  42. data/lib/cyberarm_engine/config_file.rb +46 -46
  43. data/lib/cyberarm_engine/console/command.rb +157 -157
  44. data/lib/cyberarm_engine/console/commands/help_command.rb +43 -43
  45. data/lib/cyberarm_engine/console/subcommand.rb +99 -99
  46. data/lib/cyberarm_engine/console.rb +248 -248
  47. data/lib/cyberarm_engine/event_bus.rb +39 -0
  48. data/lib/cyberarm_engine/game_object.rb +244 -244
  49. data/lib/cyberarm_engine/game_state.rb +134 -124
  50. data/lib/cyberarm_engine/gosu_ext/draw_arc.rb +111 -111
  51. data/lib/cyberarm_engine/gosu_ext/draw_circle.rb +30 -30
  52. data/lib/cyberarm_engine/gosu_ext/draw_path.rb +17 -17
  53. data/lib/cyberarm_engine/model/material.rb +21 -21
  54. data/lib/cyberarm_engine/model/mesh.rb +131 -131
  55. data/lib/cyberarm_engine/model/parser.rb +74 -74
  56. data/lib/cyberarm_engine/model/parsers/collada_parser.rb +138 -138
  57. data/lib/cyberarm_engine/model/parsers/wavefront_parser.rb +154 -154
  58. data/lib/cyberarm_engine/model.rb +216 -216
  59. data/lib/cyberarm_engine/model_cache.rb +31 -31
  60. data/lib/cyberarm_engine/notification.rb +82 -82
  61. data/lib/cyberarm_engine/notification_manager.rb +241 -241
  62. data/lib/cyberarm_engine/opengl/light.rb +52 -52
  63. data/lib/cyberarm_engine/opengl/orthographic_camera.rb +46 -46
  64. data/lib/cyberarm_engine/opengl/perspective_camera.rb +41 -41
  65. data/lib/cyberarm_engine/opengl/renderer/bounding_box_renderer.rb +249 -249
  66. data/lib/cyberarm_engine/opengl/renderer/g_buffer.rb +167 -167
  67. data/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb +307 -307
  68. data/lib/cyberarm_engine/opengl/renderer/renderer.rb +33 -33
  69. data/lib/cyberarm_engine/opengl/shader.rb +408 -408
  70. data/lib/cyberarm_engine/opengl/texture.rb +69 -69
  71. data/lib/cyberarm_engine/opengl.rb +53 -53
  72. data/lib/cyberarm_engine/pathfinding/graph.rb +64 -0
  73. data/lib/cyberarm_engine/pathfinding/graph_search.rb +6 -0
  74. data/lib/cyberarm_engine/pathfinding/graph_searches/astar_search.rb +6 -0
  75. data/lib/cyberarm_engine/pathfinding/graph_searches/breadth_first_search.rb +27 -0
  76. data/lib/cyberarm_engine/pathfinding/graph_searches/dijkstra_search.rb +6 -0
  77. data/lib/cyberarm_engine/pathfinding/graphs/grid.rb +52 -0
  78. data/lib/cyberarm_engine/ray.rb +56 -56
  79. data/lib/cyberarm_engine/result.rb +19 -0
  80. data/lib/cyberarm_engine/stats.rb +200 -200
  81. data/lib/cyberarm_engine/text.rb +288 -260
  82. data/lib/cyberarm_engine/timer.rb +27 -23
  83. data/lib/cyberarm_engine/transform.rb +296 -296
  84. data/lib/cyberarm_engine/trees/aabb_node.rb +126 -126
  85. data/lib/cyberarm_engine/trees/aabb_tree.rb +55 -55
  86. data/lib/cyberarm_engine/trees/aabb_tree_debug.rb +29 -29
  87. data/lib/cyberarm_engine/ui/border_canvas.rb +102 -102
  88. data/lib/cyberarm_engine/ui/dialog.rb +24 -0
  89. data/lib/cyberarm_engine/ui/dsl.rb +170 -142
  90. data/lib/cyberarm_engine/ui/element.rb +664 -662
  91. data/lib/cyberarm_engine/ui/elements/button.rb +99 -100
  92. data/lib/cyberarm_engine/ui/elements/check_box.rb +58 -54
  93. data/lib/cyberarm_engine/ui/elements/container.rb +413 -407
  94. data/lib/cyberarm_engine/ui/elements/edit_box.rb +200 -179
  95. data/lib/cyberarm_engine/ui/elements/edit_line.rb +299 -297
  96. data/lib/cyberarm_engine/ui/elements/flow.rb +15 -15
  97. data/lib/cyberarm_engine/ui/elements/image.rb +78 -72
  98. data/lib/cyberarm_engine/ui/elements/list_box.rb +79 -79
  99. data/lib/cyberarm_engine/ui/elements/menu.rb +27 -27
  100. data/lib/cyberarm_engine/ui/elements/menu_item.rb +6 -6
  101. data/lib/cyberarm_engine/ui/elements/progress.rb +95 -93
  102. data/lib/cyberarm_engine/ui/elements/radio.rb +6 -6
  103. data/lib/cyberarm_engine/ui/elements/slider.rb +111 -107
  104. data/lib/cyberarm_engine/ui/elements/stack.rb +11 -11
  105. data/lib/cyberarm_engine/ui/elements/text_block.rb +236 -222
  106. data/lib/cyberarm_engine/ui/elements/toggle_button.rb +71 -67
  107. data/lib/cyberarm_engine/ui/elements/widget.rb +45 -0
  108. data/lib/cyberarm_engine/ui/event.rb +54 -54
  109. data/lib/cyberarm_engine/ui/gui_state.rb +402 -326
  110. data/lib/cyberarm_engine/ui/page.rb +39 -0
  111. data/lib/cyberarm_engine/ui/style.rb +94 -61
  112. data/lib/cyberarm_engine/ui/theme.rb +237 -225
  113. data/lib/cyberarm_engine/vector.rb +312 -312
  114. data/lib/cyberarm_engine/version.rb +4 -4
  115. data/lib/cyberarm_engine/window.rb +234 -195
  116. data/lib/cyberarm_engine.rb +88 -76
  117. data/mrbgem.rake +29 -29
  118. metadata +30 -5
@@ -1,131 +1,131 @@
1
- module CyberarmEngine
2
- class IntroState < CyberarmEngine::GameState
3
- def setup
4
- @display_width = 800
5
- @display_height = 600
6
-
7
- @title_size = 56
8
- @caption_size = 24
9
-
10
- @title = CyberarmEngine::Text.new("", size: @title_size, shadow_color: 0xaa_222222, static: true)
11
- @caption = CyberarmEngine::Text.new("", size: @caption_size, shadow_color: 0xaa_222222, static: true)
12
-
13
- @spacer_width = 256
14
- @spacer_height = 6
15
- @padding = 6
16
-
17
- @cyberarm_engine_logo = get_image "#{CYBERARM_ENGINE_ROOT_PATH}/assets/textures/logo.png"
18
-
19
- @gosu_logo = generate_proxy("Gosu", "Game Library", 0xff_111111)
20
- @ruby_logo = generate_proxy("Ruby", "Programming Language", 0xff_880000)
21
- @opengl_logo = generate_proxy("OpenGL", "Graphics API", 0xff_5586a4) if RUBY_ENGINE != "mruby" && defined?(OpenGL)
22
-
23
- base_time = Gosu.milliseconds
24
-
25
- @born_time = Gosu.milliseconds
26
- @continue_after = 5_000
27
-
28
- @animators = [
29
- Animator.new(start_time: base_time += 1000, duration: 100, from: 0.0, to: 1.0, tween: :ease_in_out),
30
- Animator.new(start_time: base_time += -500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
31
- Animator.new(start_time: base_time += 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
32
- Animator.new(start_time: base_time += 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
33
- Animator.new(start_time: base_time + 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
34
- Animator.new(start_time: Gosu.milliseconds + @continue_after - 1_000, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
35
-
36
- Animator.new(start_time: Gosu.milliseconds + 250, duration: 500, from: 0.0, to: 1.0, tween: :swing_to) # CyberarmEngine LOGO
37
- ]
38
- end
39
-
40
- def draw
41
- Gosu.draw_rect(0, 0, window.width, window.height, 0xff_222222)
42
-
43
- scale = (@display_width - @padding * 2).to_f / @cyberarm_engine_logo.width * @animators.last.transition
44
-
45
- @cyberarm_engine_logo.draw_rot(
46
- window.width / 2,
47
- (window.height) / 2 - @cyberarm_engine_logo.height / 2 - @padding * 2,
48
- 2,
49
- 0,
50
- 0.5,
51
- 0.5,
52
- scale,
53
- scale
54
- )
55
-
56
- Gosu.draw_rect(
57
- window.width / 2 - (@display_width / 2 + @padding),
58
- window.height / 2 - @spacer_height / 2,
59
- @display_width + @padding,
60
- @spacer_height * @animators[0].transition,
61
- Gosu::Color::WHITE
62
- )
63
-
64
- @title.x = window.width / 2 - @title.text_width / 2
65
- @title.y = (window.height / 2 + (@spacer_height / 2) + @padding) * @animators[1].transition
66
- @title.text = "Powered By"
67
-
68
- Gosu.clip_to(0, window.height / 2 + (@spacer_height / 2), window.width, @title.height) do
69
- @title.draw
70
- end
71
-
72
- y = @title.y + @title.height * 2
73
-
74
- Gosu.clip_to(0, y, window.width, @gosu_logo.height) do
75
- Gosu.translate(@opengl_logo.nil? ? @ruby_logo.width / 2 : 0, 0) do
76
- @gosu_logo.draw(
77
- window.width.to_f / 2 - @ruby_logo.width / 2 - (@ruby_logo.width - @padding),
78
- y * @animators[2].transition,
79
- 2
80
- )
81
- @ruby_logo.draw(
82
- window.width.to_f / 2 - @ruby_logo.width / 2,
83
- y * @animators[3].transition,
84
- 2
85
- )
86
- @opengl_logo&.draw(
87
- window.width.to_f / 2 - @ruby_logo.width / 2 + (@ruby_logo.width - @padding),
88
- y * @animators[4].transition,
89
- 2
90
- )
91
- end
92
- end
93
-
94
- Gosu.draw_rect(0, 0, window.width, window.height, Gosu::Color.rgba(0, 0, 0, 255 * @animators[5].transition), 10_000)
95
- end
96
-
97
- def update
98
- @animators.each(&:update)
99
-
100
- return unless Gosu.milliseconds - @born_time >= @continue_after
101
-
102
- pop_state
103
- push_state(@options[:forward], @options[:forward_options] || {}) if @options[:forward]
104
- end
105
-
106
- def button_down(_id)
107
- @continue_after = 0
108
- end
109
-
110
- def generate_proxy(title, caption, color_hint)
111
- @title.text = title
112
- @caption.text = caption
113
-
114
- width = @spacer_width + 2 * @padding
115
- height = @title_size + @caption_size + @spacer_height + 2 * @padding + @spacer_height
116
-
117
- Gosu.record(width.ceil, height.ceil) do
118
- @title.x = (width - @padding * 2) / 2 - @title.text_width / 2
119
- @title.y = @padding
120
- @title.draw
121
-
122
- Gosu.draw_rect(0, @padding + @title_size + @padding, @spacer_width, @spacer_height, Gosu::Color::WHITE)
123
- Gosu.draw_rect(1, @padding + @title_size + @padding + 1, @spacer_width - 2, @spacer_height - 2, color_hint)
124
-
125
- @caption.x = (width - @padding * 2) / 2 - @caption.text_width / 2
126
- @caption.y = @padding + @title_size + @padding + @spacer_height + @padding
127
- @caption.draw
128
- end
129
- end
130
- end
1
+ module CyberarmEngine
2
+ class IntroState < CyberarmEngine::GameState
3
+ def setup
4
+ @display_width = 800
5
+ @display_height = 600
6
+
7
+ @title_size = 56
8
+ @caption_size = 24
9
+
10
+ @title = CyberarmEngine::Text.new("", size: @title_size, shadow_color: 0xaa_222222, static: true)
11
+ @caption = CyberarmEngine::Text.new("", size: @caption_size, shadow_color: 0xaa_222222, static: true)
12
+
13
+ @spacer_width = 256
14
+ @spacer_height = 6
15
+ @padding = 6
16
+
17
+ @cyberarm_engine_logo = get_image "#{CYBERARM_ENGINE_ROOT_PATH}/assets/textures/logo.png"
18
+
19
+ @gosu_logo = generate_proxy("Gosu", "Game Library", 0xff_111111)
20
+ @ruby_logo = generate_proxy("Ruby", "Programming Language", 0xff_880000)
21
+ @opengl_logo = generate_proxy("OpenGL", "Graphics API", 0xff_5586a4) if RUBY_ENGINE != "mruby" && defined?(OpenGL)
22
+
23
+ base_time = Gosu.milliseconds
24
+
25
+ @born_time = Gosu.milliseconds
26
+ @continue_after = 5_000
27
+
28
+ @animators = [
29
+ Animator.new(start_time: base_time += 1000, duration: 100, from: 0.0, to: 1.0, tween: :ease_in_out),
30
+ Animator.new(start_time: base_time += -500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
31
+ Animator.new(start_time: base_time += 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
32
+ Animator.new(start_time: base_time += 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
33
+ Animator.new(start_time: base_time + 500, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
34
+ Animator.new(start_time: Gosu.milliseconds + @continue_after - 1_000, duration: 1_000, from: 0.0, to: 1.0, tween: :ease_in_out),
35
+
36
+ Animator.new(start_time: Gosu.milliseconds + 250, duration: 500, from: 0.0, to: 1.0, tween: :swing_to) # CyberarmEngine LOGO
37
+ ]
38
+ end
39
+
40
+ def draw
41
+ Gosu.draw_rect(0, 0, window.width, window.height, 0xff_222222)
42
+
43
+ scale = (@display_width - @padding * 2).to_f / @cyberarm_engine_logo.width * @animators.last.transition
44
+
45
+ @cyberarm_engine_logo.draw_rot(
46
+ window.width / 2,
47
+ (window.height) / 2 - @cyberarm_engine_logo.height / 2 - @padding * 2,
48
+ 2,
49
+ 0,
50
+ 0.5,
51
+ 0.5,
52
+ scale,
53
+ scale
54
+ )
55
+
56
+ Gosu.draw_rect(
57
+ window.width / 2 - (@display_width / 2 + @padding),
58
+ window.height / 2 - @spacer_height / 2,
59
+ @display_width + @padding,
60
+ @spacer_height * @animators[0].transition,
61
+ Gosu::Color::WHITE
62
+ )
63
+
64
+ @title.x = window.width / 2 - @title.text_width / 2
65
+ @title.y = (window.height / 2 + (@spacer_height / 2) + @padding) * @animators[1].transition
66
+ @title.text = "Powered By"
67
+
68
+ Gosu.clip_to(0, window.height / 2 + (@spacer_height / 2), window.width, @title.height) do
69
+ @title.draw
70
+ end
71
+
72
+ y = @title.y + @title.height * 2
73
+
74
+ Gosu.clip_to(0, y, window.width, @gosu_logo.height) do
75
+ Gosu.translate(@opengl_logo.nil? ? @ruby_logo.width / 2 : 0, 0) do
76
+ @gosu_logo.draw(
77
+ window.width.to_f / 2 - @ruby_logo.width / 2 - (@ruby_logo.width - @padding),
78
+ y * @animators[2].transition,
79
+ 2
80
+ )
81
+ @ruby_logo.draw(
82
+ window.width.to_f / 2 - @ruby_logo.width / 2,
83
+ y * @animators[3].transition,
84
+ 2
85
+ )
86
+ @opengl_logo&.draw(
87
+ window.width.to_f / 2 - @ruby_logo.width / 2 + (@ruby_logo.width - @padding),
88
+ y * @animators[4].transition,
89
+ 2
90
+ )
91
+ end
92
+ end
93
+
94
+ Gosu.draw_rect(0, 0, window.width, window.height, Gosu::Color.rgba(0, 0, 0, 255 * @animators[5].transition), 10_000)
95
+ end
96
+
97
+ def update
98
+ @animators.each(&:update)
99
+
100
+ return unless Gosu.milliseconds - @born_time >= @continue_after
101
+
102
+ pop_state
103
+ push_state(@options[:forward], @options[:forward_options] || {}) if @options[:forward]
104
+ end
105
+
106
+ def button_down(_id)
107
+ @continue_after = 0
108
+ end
109
+
110
+ def generate_proxy(title, caption, color_hint)
111
+ @title.text = title
112
+ @caption.text = caption
113
+
114
+ width = @spacer_width + 2 * @padding
115
+ height = @title_size + @caption_size + @spacer_height + 2 * @padding + @spacer_height
116
+
117
+ Gosu.record(width.ceil, height.ceil) do
118
+ @title.x = (width - @padding * 2) / 2 - @title.text_width / 2
119
+ @title.y = @padding
120
+ @title.draw
121
+
122
+ Gosu.draw_rect(0, @padding + @title_size + @padding, @spacer_width, @spacer_height, Gosu::Color::WHITE)
123
+ Gosu.draw_rect(1, @padding + @title_size + @padding + 1, @spacer_width - 2, @spacer_height - 2, color_hint)
124
+
125
+ @caption.x = (width - @padding * 2) / 2 - @caption.text_width / 2
126
+ @caption.y = @padding + @title_size + @padding + @spacer_height + @padding
127
+ @caption.draw
128
+ end
129
+ end
130
+ end
131
131
  end
@@ -1,123 +1,123 @@
1
- require "excon"
2
-
3
- module CyberarmEngine
4
- module Cache
5
- class DownloadManager
6
- attr_reader :downloads
7
-
8
- def initialize(max_parallel_downloads: 4)
9
- @max_parallel_downloads = max_parallel_downloads
10
- @downloads = []
11
- end
12
-
13
- def download(url:, save_as: nil, &callback)
14
- uri = URI(url)
15
- save_as ||= "filename_path" # TODO: if no save_as path is provided, then get one from the Cache controller
16
-
17
- @downloads << Download.new(uri: uri, save_as: save_as, callback: callback)
18
- end
19
-
20
- def status
21
- if active_downloads > 0
22
- :busy
23
- else
24
- :idle
25
- end
26
- end
27
-
28
- def progress
29
- remaining_bytes = @downloads.map { |d| d.remaining_bytes }.sum
30
- total_bytes = @downloads.map { |d| d.total_bytes }.sum
31
-
32
- v = 1.0 - (remaining_bytes.to_f / total_bytes)
33
- return 0.0 if v.nan?
34
-
35
- v
36
- end
37
-
38
- def active_downloads
39
- @downloads.select { |d| %i[pending downloading].include?(d.status) }
40
- end
41
-
42
- def update
43
- @downloads.each do |download|
44
- if download.status == :pending && active_downloads.size <= @max_parallel_downloads
45
- download.status = :downloading
46
- Thread.start { download.download }
47
- end
48
- end
49
- end
50
-
51
- def prune
52
- @downloads.delete_if { |d| d.status == :finished || d.status == :failed }
53
- end
54
-
55
- class Download
56
- attr_accessor :status
57
- attr_reader :uri, :save_as, :callback, :remaining_bytes, :total_downloaded_bytes, :total_bytes,
58
- :error_message, :started_at, :finished_at
59
-
60
- def initialize(uri:, save_as:, callback: nil)
61
- @uri = uri
62
- @save_as = save_as
63
- @callback = callback
64
-
65
- @status = :pending
66
-
67
- @remaining_bytes = 0.0
68
- @total_downloaded_bytes = 0.0
69
- @total_bytes = 0.0
70
-
71
- @error_message = ""
72
- end
73
-
74
- def progress
75
- v = 1.0 - (@remaining_bytes.to_f / total_bytes)
76
- return 0.0 if v.nan?
77
-
78
- v
79
- end
80
-
81
- def download
82
- @status = :downloading
83
- @started_at = Time.now # TODO: monotonic time
84
-
85
- io = File.open(@save_as, "w")
86
- streamer = lambda do |chunk, remaining_bytes, total_bytes|
87
- io.write(chunk)
88
-
89
- @remaining_bytes = remaining_bytes.to_f
90
- @total_downloaded_bytes += chunk.size
91
- @total_bytes = total_bytes.to_f
92
- end
93
-
94
- begin
95
- response = Excon.get(
96
- @uri.to_s,
97
- middlewares: Excon.defaults[:middlewares] + [Excon::Middleware::RedirectFollower],
98
- response_block: streamer
99
- )
100
-
101
- if response.status == 200
102
- @status = :finished
103
- @finished_at = Time.now # TODO: monotonic time
104
- @callback.call(self) if @callback
105
- else
106
- @error_message = "Got a non 200 HTTP status of #{response.status}"
107
- @status = :failed
108
- @finished_at = Time.now # TODO: monotonic time
109
- @callback.call(self) if @callback
110
- end
111
- rescue StandardError => e # TODO: cherrypick errors to cature
112
- @status = :failed
113
- @finished_at = Time.now # TODO: monotonic time
114
- @error_message = e.message
115
- @callback.call(self) if @callback
116
- end
117
- ensure
118
- io.close if io
119
- end
120
- end
121
- end
122
- end
123
- end
1
+ require "excon"
2
+
3
+ module CyberarmEngine
4
+ module Cache
5
+ class DownloadManager
6
+ attr_reader :downloads
7
+
8
+ def initialize(max_parallel_downloads: 4)
9
+ @max_parallel_downloads = max_parallel_downloads
10
+ @downloads = []
11
+ end
12
+
13
+ def download(url:, save_as: nil, &callback)
14
+ uri = URI(url)
15
+ save_as ||= "filename_path" # TODO: if no save_as path is provided, then get one from the Cache controller
16
+
17
+ @downloads << Download.new(uri: uri, save_as: save_as, callback: callback)
18
+ end
19
+
20
+ def status
21
+ if active_downloads > 0
22
+ :busy
23
+ else
24
+ :idle
25
+ end
26
+ end
27
+
28
+ def progress
29
+ remaining_bytes = @downloads.map { |d| d.remaining_bytes }.sum
30
+ total_bytes = @downloads.map { |d| d.total_bytes }.sum
31
+
32
+ v = 1.0 - (remaining_bytes.to_f / total_bytes)
33
+ return 0.0 if v.nan?
34
+
35
+ v
36
+ end
37
+
38
+ def active_downloads
39
+ @downloads.select { |d| %i[pending downloading].include?(d.status) }
40
+ end
41
+
42
+ def update
43
+ @downloads.each do |download|
44
+ if download.status == :pending && active_downloads.size <= @max_parallel_downloads
45
+ download.status = :downloading
46
+ Thread.start { download.download }
47
+ end
48
+ end
49
+ end
50
+
51
+ def prune
52
+ @downloads.delete_if { |d| d.status == :finished || d.status == :failed }
53
+ end
54
+
55
+ class Download
56
+ attr_accessor :status
57
+ attr_reader :uri, :save_as, :callback, :remaining_bytes, :total_downloaded_bytes, :total_bytes,
58
+ :error_message, :started_at, :finished_at
59
+
60
+ def initialize(uri:, save_as:, callback: nil)
61
+ @uri = uri
62
+ @save_as = save_as
63
+ @callback = callback
64
+
65
+ @status = :pending
66
+
67
+ @remaining_bytes = 0.0
68
+ @total_downloaded_bytes = 0.0
69
+ @total_bytes = 0.0
70
+
71
+ @error_message = ""
72
+ end
73
+
74
+ def progress
75
+ v = 1.0 - (@remaining_bytes.to_f / total_bytes)
76
+ return 0.0 if v.nan?
77
+
78
+ v
79
+ end
80
+
81
+ def download
82
+ @status = :downloading
83
+ @started_at = Time.now # TODO: monotonic time
84
+
85
+ io = File.open(@save_as, "w")
86
+ streamer = lambda do |chunk, remaining_bytes, total_bytes|
87
+ io.write(chunk)
88
+
89
+ @remaining_bytes = remaining_bytes.to_f
90
+ @total_downloaded_bytes += chunk.size
91
+ @total_bytes = total_bytes.to_f
92
+ end
93
+
94
+ begin
95
+ response = Excon.get(
96
+ @uri.to_s,
97
+ middlewares: Excon.defaults[:middlewares] + [Excon::Middleware::RedirectFollower],
98
+ response_block: streamer
99
+ )
100
+
101
+ if response.status == 200
102
+ @status = :finished
103
+ @finished_at = Time.now # TODO: monotonic time
104
+ @callback.call(self) if @callback
105
+ else
106
+ @error_message = "Got a non 200 HTTP status of #{response.status}"
107
+ @status = :failed
108
+ @finished_at = Time.now # TODO: monotonic time
109
+ @callback.call(self) if @callback
110
+ end
111
+ rescue StandardError => e # TODO: cherrypick errors to cature
112
+ @status = :failed
113
+ @finished_at = Time.now # TODO: monotonic time
114
+ @error_message = e.message
115
+ @callback.call(self) if @callback
116
+ end
117
+ ensure
118
+ io.close if io
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -1,4 +1,4 @@
1
- module CyberarmEngine
2
- module Cache
3
- end
4
- end
1
+ module CyberarmEngine
2
+ module Cache
3
+ end
4
+ end