cyberarm_engine 0.13.0 → 0.17.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.
- checksums.yaml +4 -4
- data/.gitignore +8 -8
- data/.rubocop.yml +8 -0
- data/.travis.yml +5 -5
- data/Gemfile +6 -6
- data/LICENSE.txt +21 -21
- data/README.md +73 -43
- data/Rakefile +10 -10
- data/assets/textures/default.png +0 -0
- data/bin/console +14 -14
- data/bin/setup +8 -8
- data/cyberarm_engine.gemspec +39 -36
- data/lib/cyberarm_engine.rb +64 -47
- data/lib/cyberarm_engine/animator.rb +56 -54
- data/lib/cyberarm_engine/background.rb +179 -175
- data/lib/cyberarm_engine/background_nine_slice.rb +125 -0
- data/lib/cyberarm_engine/bounding_box.rb +150 -150
- data/lib/cyberarm_engine/cache.rb +4 -0
- data/lib/cyberarm_engine/cache/download_manager.rb +121 -0
- data/lib/cyberarm_engine/common.rb +96 -96
- data/lib/cyberarm_engine/config_file.rb +46 -0
- data/lib/cyberarm_engine/game_object.rb +248 -257
- data/lib/cyberarm_engine/game_state.rb +92 -89
- data/lib/cyberarm_engine/model.rb +207 -0
- data/lib/cyberarm_engine/model/material.rb +21 -0
- data/lib/cyberarm_engine/model/model_object.rb +131 -0
- data/lib/cyberarm_engine/model/parser.rb +74 -0
- data/lib/cyberarm_engine/model/parsers/collada_parser.rb +138 -0
- data/lib/cyberarm_engine/model/parsers/wavefront_parser.rb +154 -0
- data/lib/cyberarm_engine/model_cache.rb +31 -0
- data/lib/cyberarm_engine/opengl.rb +28 -0
- data/lib/cyberarm_engine/opengl/light.rb +50 -0
- data/lib/cyberarm_engine/opengl/orthographic_camera.rb +46 -0
- data/lib/cyberarm_engine/opengl/perspective_camera.rb +38 -0
- data/lib/cyberarm_engine/opengl/renderer/bounding_box_renderer.rb +249 -0
- data/lib/cyberarm_engine/opengl/renderer/g_buffer.rb +164 -0
- data/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb +289 -0
- data/lib/cyberarm_engine/opengl/renderer/renderer.rb +22 -0
- data/lib/cyberarm_engine/opengl/shader.rb +406 -0
- data/lib/cyberarm_engine/opengl/texture.rb +69 -0
- data/lib/cyberarm_engine/ray.rb +56 -56
- data/lib/cyberarm_engine/stats.rb +21 -0
- data/lib/cyberarm_engine/text.rb +160 -146
- data/lib/cyberarm_engine/timer.rb +23 -23
- data/lib/cyberarm_engine/transform.rb +296 -273
- data/lib/cyberarm_engine/ui/border_canvas.rb +102 -101
- data/lib/cyberarm_engine/ui/dsl.rb +138 -99
- data/lib/cyberarm_engine/ui/element.rb +315 -276
- data/lib/cyberarm_engine/ui/elements/button.rb +160 -67
- data/lib/cyberarm_engine/ui/elements/check_box.rb +51 -59
- data/lib/cyberarm_engine/ui/elements/container.rb +256 -176
- data/lib/cyberarm_engine/ui/elements/edit_box.rb +179 -0
- data/lib/cyberarm_engine/ui/elements/edit_line.rb +262 -172
- data/lib/cyberarm_engine/ui/elements/flow.rb +15 -17
- data/lib/cyberarm_engine/ui/elements/image.rb +72 -52
- data/lib/cyberarm_engine/ui/elements/label.rb +156 -50
- data/lib/cyberarm_engine/ui/elements/list_box.rb +82 -0
- data/lib/cyberarm_engine/ui/elements/progress.rb +51 -50
- data/lib/cyberarm_engine/ui/elements/radio.rb +6 -0
- data/lib/cyberarm_engine/ui/elements/slider.rb +104 -0
- data/lib/cyberarm_engine/ui/elements/stack.rb +11 -13
- data/lib/cyberarm_engine/ui/elements/text_block.rb +156 -0
- data/lib/cyberarm_engine/ui/elements/toggle_button.rb +65 -56
- data/lib/cyberarm_engine/ui/event.rb +47 -47
- data/lib/cyberarm_engine/ui/gui_state.rb +226 -135
- data/lib/cyberarm_engine/ui/style.rb +38 -37
- data/lib/cyberarm_engine/ui/theme.rb +182 -120
- data/lib/cyberarm_engine/vector.rb +293 -203
- data/lib/cyberarm_engine/version.rb +4 -4
- data/lib/cyberarm_engine/{engine.rb → window.rb} +114 -101
- metadata +88 -18
- data/lib/cyberarm_engine/gosu_ext/circle.rb +0 -9
- data/lib/cyberarm_engine/shader.rb +0 -262
@@ -0,0 +1,125 @@
|
|
1
|
+
module CyberarmEngine
|
2
|
+
class BackgroundNineSlice
|
3
|
+
include CyberarmEngine::Common
|
4
|
+
attr_accessor :x, :y, :z, :width, :height
|
5
|
+
|
6
|
+
def initialize(image_path:, x: 0, y: 0, z: 0, width: 64, height: 64, mode: :tiled, left: 4, top: 4, right: 56, bottom: 56)
|
7
|
+
@image = get_image(image_path)
|
8
|
+
|
9
|
+
@x = x
|
10
|
+
@y = y
|
11
|
+
@z = z
|
12
|
+
|
13
|
+
@width = width
|
14
|
+
@height = height
|
15
|
+
|
16
|
+
@mode = mode
|
17
|
+
|
18
|
+
@left = left
|
19
|
+
@top = top
|
20
|
+
@right = right
|
21
|
+
@bottom = bottom
|
22
|
+
|
23
|
+
nine_slice
|
24
|
+
end
|
25
|
+
|
26
|
+
def nine_slice
|
27
|
+
@segment_top_left = Gosu.render(@left, @top) { @image.draw(0, 0, 0) }
|
28
|
+
@segment_top_right = Gosu.render(@image.width - @right, @top) { @image.draw(-@right, 0, 0) }
|
29
|
+
|
30
|
+
@segment_left = Gosu.render(@left, @bottom - @top) { @image.draw(0, -@top, 0) }
|
31
|
+
@segment_right = Gosu.render(@image.width - @right, @bottom - @top) { @image.draw(-@right, -@top, 0) }
|
32
|
+
|
33
|
+
@segment_bottom_left = Gosu.render(@left, @image.height - @bottom) { @image.draw(0, -@bottom, 0) }
|
34
|
+
@segment_bottom_right = Gosu.render(@image.width - @right, @image.height - @bottom) { @image.draw(-@right, -@bottom, 0) }
|
35
|
+
|
36
|
+
@segment_top = Gosu.render(@right - @left, @top) { @image.draw(-@left, 0, 0) }
|
37
|
+
@segment_bottom = Gosu.render(@right - @left, @image.height - @bottom) { @image.draw(-@left, -@bottom, 0) }
|
38
|
+
|
39
|
+
@segment_middle = Gosu.render(@right - @left, @bottom - @top) { @image.draw(-@left, -@top, 0) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def cx
|
43
|
+
@x + @left
|
44
|
+
end
|
45
|
+
|
46
|
+
def cy
|
47
|
+
@y + @top
|
48
|
+
end
|
49
|
+
|
50
|
+
def cwidth
|
51
|
+
@cx - @width
|
52
|
+
end
|
53
|
+
|
54
|
+
def cheight
|
55
|
+
@cy - @height
|
56
|
+
end
|
57
|
+
|
58
|
+
def width_scale
|
59
|
+
width_scale = (@width - (@left + (@image.width - @right))).to_f / (@right - @left)
|
60
|
+
end
|
61
|
+
|
62
|
+
def height_scale
|
63
|
+
height_scale = (@height - (@top + (@image.height - @bottom))).to_f / (@bottom - @top)
|
64
|
+
end
|
65
|
+
|
66
|
+
def draw
|
67
|
+
@mode == :tiled ? draw_tiled : draw_stretched
|
68
|
+
end
|
69
|
+
|
70
|
+
def draw_stretched
|
71
|
+
@segment_top_left.draw(@x, @y, @z)
|
72
|
+
@segment_top.draw(@x + @segment_top_left.width, @y, @z, width_scale) # SCALE X
|
73
|
+
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z)
|
74
|
+
|
75
|
+
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top, @z, 1, height_scale) # SCALE Y
|
76
|
+
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z)
|
77
|
+
@segment_bottom.draw(@x + @segment_bottom_left.width, (@y + @height) - @segment_bottom.height, @z, width_scale) # SCALE X
|
78
|
+
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z)
|
79
|
+
@segment_left.draw(@x, @y + @top, @z, 1, height_scale) # SCALE Y
|
80
|
+
@segment_middle.draw(@x + @segment_top_left.width, @y + @segment_top.height, @z, width_scale, height_scale) # SCALE X and SCALE Y
|
81
|
+
end
|
82
|
+
|
83
|
+
def draw_tiled
|
84
|
+
@segment_top_left.draw(@x, @y, @z)
|
85
|
+
|
86
|
+
Gosu.clip_to(@x + @segment_top_left.width, @y, @segment_top.width * width_scale, @segment_top.height) do
|
87
|
+
width_scale.ceil.times do |i|
|
88
|
+
@segment_top.draw(@x + @segment_top_left.width + (@segment_top.width * i), @y, @z) # SCALE X
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
@segment_top_right.draw((@x + @width) - @segment_top_right.width, @y, @z)
|
93
|
+
|
94
|
+
Gosu.clip_to(@x + @width - @segment_top_right.width, @y + @top, @segment_right.width, @segment_right.height * height_scale) do
|
95
|
+
height_scale.ceil.times do |i|
|
96
|
+
@segment_right.draw((@x + @width) - @segment_right.width, @y + @top + (@segment_right.height * i), @z) # SCALE Y
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
@segment_bottom_right.draw((@x + @width) - @segment_bottom_right.width, @y + @height - @segment_bottom_right.height, @z)
|
101
|
+
|
102
|
+
Gosu.clip_to(@x + @segment_top_left.width, @y + @height - @segment_bottom.height, @segment_top.width * width_scale, @segment_bottom.height) do
|
103
|
+
width_scale.ceil.times do |i|
|
104
|
+
@segment_bottom.draw(@x + @segment_bottom_left.width + (@segment_bottom.width * i), (@y + @height) - @segment_bottom.height, @z) # SCALE X
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
@segment_bottom_left.draw(@x, (@y + @height) - @segment_bottom_left.height, @z)
|
109
|
+
|
110
|
+
Gosu.clip_to(@x, @y + @top, @segment_left.width, @segment_left.height * height_scale) do
|
111
|
+
height_scale.ceil.times do |i|
|
112
|
+
@segment_left.draw(@x, @y + @top + (@segment_left.height * i), @z) # SCALE Y
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
Gosu.clip_to(@x + @segment_top_left.width, @y + @segment_top.height, @width - (@segment_left.width + @segment_right.width), @height - (@segment_top.height + @segment_bottom.height)) do
|
117
|
+
height_scale.ceil.times do |y|
|
118
|
+
width_scale.ceil.times do |x|
|
119
|
+
@segment_middle.draw(@x + @segment_top_left.width + (@segment_middle.width * x), @y + @segment_top.height + (@segment_middle.height * y), @z) # SCALE X and SCALE Y
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -1,150 +1,150 @@
|
|
1
|
-
module CyberarmEngine
|
2
|
-
class BoundingBox
|
3
|
-
attr_accessor :min, :max
|
4
|
-
|
5
|
-
def initialize(*args)
|
6
|
-
case args.size
|
7
|
-
when 0
|
8
|
-
@min = Vector.new(0, 0, 0)
|
9
|
-
@max = Vector.new(0, 0, 0)
|
10
|
-
when 2
|
11
|
-
@min = args.first.clone
|
12
|
-
@max = args.last.clone
|
13
|
-
when 4
|
14
|
-
@min = Vector.new(args[0], args[1], 0)
|
15
|
-
@max = Vector.new(args[2], args[3], 0)
|
16
|
-
when 6
|
17
|
-
@min = Vector.new(args[0], args[1], args[2])
|
18
|
-
@max = Vector.new(args[3], args[4], args[5])
|
19
|
-
else
|
20
|
-
raise "Invalid number of arguments! Got: #{args.size}, expected: 0, 2, 4, or 6."
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def ==(other)
|
25
|
-
@min == other.min &&
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
# returns a new bounding box that includes both bounding boxes
|
30
|
-
def union(other)
|
31
|
-
temp = BoundingBox.new
|
32
|
-
temp.min.x = [@min.x, other.min.x].min
|
33
|
-
temp.min.y = [@min.y, other.min.y].min
|
34
|
-
temp.min.z = [@min.z, other.min.z].min
|
35
|
-
|
36
|
-
temp.max.x = [@max.x, other.max.x].max
|
37
|
-
temp.max.y = [@max.y, other.max.y].max
|
38
|
-
temp.max.z = [@max.z, other.max.z].max
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
# returns the difference between both bounding boxes
|
44
|
-
def difference(other)
|
45
|
-
temp = BoundingBox.new
|
46
|
-
temp.min = @min - other.min
|
47
|
-
temp.max = @max - other.max
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
# returns whether bounding box intersects other
|
53
|
-
def intersect?(other)
|
54
|
-
if other.is_a?(Ray)
|
55
|
-
other.intersect?(self)
|
56
|
-
elsif other.is_a?(BoundingBox)
|
57
|
-
(@min.x <= other.max.x && @max.x >= other.min.x) &&
|
58
|
-
|
59
|
-
|
60
|
-
else
|
61
|
-
raise "Unknown collider: #{other.class}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# does this bounding box envelop other bounding box? (inclusive of border)
|
66
|
-
def contains?(other)
|
67
|
-
other.min.x >= min.x && other.min.y >= min.y && other.min.z >= min.z &&
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
# returns whether the 3D vector is inside of the bounding box
|
72
|
-
def inside?(vector)
|
73
|
-
(vector.x.between?(@min.x, @max.x) || vector.x.between?(@max.x, @min.x)) &&
|
74
|
-
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
# returns whether the 2D vector is inside of the bounding box
|
79
|
-
def point?(vector)
|
80
|
-
(vector.x.between?(@min.x, @max.x) || vector.x.between?(@max.x, @min.x)) &&
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
def volume
|
85
|
-
width * height * depth
|
86
|
-
end
|
87
|
-
|
88
|
-
def width
|
89
|
-
@max.x - @min.x
|
90
|
-
end
|
91
|
-
|
92
|
-
def height
|
93
|
-
@max.y - @min.y
|
94
|
-
end
|
95
|
-
|
96
|
-
def depth
|
97
|
-
@max.z - @min.z
|
98
|
-
end
|
99
|
-
|
100
|
-
def normalize(entity)
|
101
|
-
temp = BoundingBox.new
|
102
|
-
temp.min.x = @min.x.to_f * entity.scale.x
|
103
|
-
temp.min.y = @min.y.to_f * entity.scale.y
|
104
|
-
temp.min.z = @min.z.to_f * entity.scale.z
|
105
|
-
|
106
|
-
temp.max.x = @max.x.to_f * entity.scale.x
|
107
|
-
temp.max.y = @max.y.to_f * entity.scale.y
|
108
|
-
temp.max.z = @max.z.to_f * entity.scale.z
|
109
|
-
|
110
|
-
|
111
|
-
end
|
112
|
-
|
113
|
-
def normalize_with_offset(entity)
|
114
|
-
temp = BoundingBox.new
|
115
|
-
temp.min.x = @min.x.to_f * entity.scale.x + entity.position.x
|
116
|
-
temp.min.y = @min.y.to_f * entity.scale.y + entity.position.y
|
117
|
-
temp.min.z = @min.z.to_f * entity.scale.z + entity.position.z
|
118
|
-
|
119
|
-
temp.max.x = @max.x.to_f * entity.scale.x + entity.position.x
|
120
|
-
temp.max.y = @max.y.to_f * entity.scale.y + entity.position.y
|
121
|
-
temp.max.z = @max.z.to_f * entity.scale.z + entity.position.z
|
122
|
-
|
123
|
-
|
124
|
-
end
|
125
|
-
|
126
|
-
def +(other)
|
127
|
-
box = BoundingBox.new
|
128
|
-
box.min =
|
129
|
-
box.min =
|
130
|
-
|
131
|
-
|
132
|
-
end
|
133
|
-
|
134
|
-
def -(other)
|
135
|
-
box = BoundingBox.new
|
136
|
-
box.min =
|
137
|
-
box.min =
|
138
|
-
|
139
|
-
|
140
|
-
end
|
141
|
-
|
142
|
-
def sum
|
143
|
-
@min.sum + @max.sum
|
144
|
-
end
|
145
|
-
|
146
|
-
def clone
|
147
|
-
BoundingBox.new(@min.x, @min.y, @min.z, @max.x, @max.y, @max.z)
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
1
|
+
module CyberarmEngine
|
2
|
+
class BoundingBox
|
3
|
+
attr_accessor :min, :max
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
case args.size
|
7
|
+
when 0
|
8
|
+
@min = Vector.new(0, 0, 0)
|
9
|
+
@max = Vector.new(0, 0, 0)
|
10
|
+
when 2
|
11
|
+
@min = args.first.clone
|
12
|
+
@max = args.last.clone
|
13
|
+
when 4
|
14
|
+
@min = Vector.new(args[0], args[1], 0)
|
15
|
+
@max = Vector.new(args[2], args[3], 0)
|
16
|
+
when 6
|
17
|
+
@min = Vector.new(args[0], args[1], args[2])
|
18
|
+
@max = Vector.new(args[3], args[4], args[5])
|
19
|
+
else
|
20
|
+
raise "Invalid number of arguments! Got: #{args.size}, expected: 0, 2, 4, or 6."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
@min == other.min &&
|
26
|
+
@max == other.max
|
27
|
+
end
|
28
|
+
|
29
|
+
# returns a new bounding box that includes both bounding boxes
|
30
|
+
def union(other)
|
31
|
+
temp = BoundingBox.new
|
32
|
+
temp.min.x = [@min.x, other.min.x].min
|
33
|
+
temp.min.y = [@min.y, other.min.y].min
|
34
|
+
temp.min.z = [@min.z, other.min.z].min
|
35
|
+
|
36
|
+
temp.max.x = [@max.x, other.max.x].max
|
37
|
+
temp.max.y = [@max.y, other.max.y].max
|
38
|
+
temp.max.z = [@max.z, other.max.z].max
|
39
|
+
|
40
|
+
temp
|
41
|
+
end
|
42
|
+
|
43
|
+
# returns the difference between both bounding boxes
|
44
|
+
def difference(other)
|
45
|
+
temp = BoundingBox.new
|
46
|
+
temp.min = @min - other.min
|
47
|
+
temp.max = @max - other.max
|
48
|
+
|
49
|
+
temp
|
50
|
+
end
|
51
|
+
|
52
|
+
# returns whether bounding box intersects other
|
53
|
+
def intersect?(other)
|
54
|
+
if other.is_a?(Ray)
|
55
|
+
other.intersect?(self)
|
56
|
+
elsif other.is_a?(BoundingBox)
|
57
|
+
(@min.x <= other.max.x && @max.x >= other.min.x) &&
|
58
|
+
(@min.y <= other.max.y && @max.y >= other.min.y) &&
|
59
|
+
(@min.z <= other.max.z && @max.z >= other.min.z)
|
60
|
+
else
|
61
|
+
raise "Unknown collider: #{other.class}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# does this bounding box envelop other bounding box? (inclusive of border)
|
66
|
+
def contains?(other)
|
67
|
+
other.min.x >= min.x && other.min.y >= min.y && other.min.z >= min.z &&
|
68
|
+
other.max.x <= max.x && other.max.y <= max.y && other.max.z <= max.z
|
69
|
+
end
|
70
|
+
|
71
|
+
# returns whether the 3D vector is inside of the bounding box
|
72
|
+
def inside?(vector)
|
73
|
+
(vector.x.between?(@min.x, @max.x) || vector.x.between?(@max.x, @min.x)) &&
|
74
|
+
(vector.y.between?(@min.y, @max.y) || vector.y.between?(@max.y, @min.y)) &&
|
75
|
+
(vector.z.between?(@min.z, @max.z) || vector.z.between?(@max.z, @min.z))
|
76
|
+
end
|
77
|
+
|
78
|
+
# returns whether the 2D vector is inside of the bounding box
|
79
|
+
def point?(vector)
|
80
|
+
(vector.x.between?(@min.x, @max.x) || vector.x.between?(@max.x, @min.x)) &&
|
81
|
+
(vector.y.between?(@min.y, @max.y) || vector.y.between?(@max.y, @min.y))
|
82
|
+
end
|
83
|
+
|
84
|
+
def volume
|
85
|
+
width * height * depth
|
86
|
+
end
|
87
|
+
|
88
|
+
def width
|
89
|
+
@max.x - @min.x
|
90
|
+
end
|
91
|
+
|
92
|
+
def height
|
93
|
+
@max.y - @min.y
|
94
|
+
end
|
95
|
+
|
96
|
+
def depth
|
97
|
+
@max.z - @min.z
|
98
|
+
end
|
99
|
+
|
100
|
+
def normalize(entity)
|
101
|
+
temp = BoundingBox.new
|
102
|
+
temp.min.x = @min.x.to_f * entity.scale.x
|
103
|
+
temp.min.y = @min.y.to_f * entity.scale.y
|
104
|
+
temp.min.z = @min.z.to_f * entity.scale.z
|
105
|
+
|
106
|
+
temp.max.x = @max.x.to_f * entity.scale.x
|
107
|
+
temp.max.y = @max.y.to_f * entity.scale.y
|
108
|
+
temp.max.z = @max.z.to_f * entity.scale.z
|
109
|
+
|
110
|
+
temp
|
111
|
+
end
|
112
|
+
|
113
|
+
def normalize_with_offset(entity)
|
114
|
+
temp = BoundingBox.new
|
115
|
+
temp.min.x = @min.x.to_f * entity.scale.x + entity.position.x
|
116
|
+
temp.min.y = @min.y.to_f * entity.scale.y + entity.position.y
|
117
|
+
temp.min.z = @min.z.to_f * entity.scale.z + entity.position.z
|
118
|
+
|
119
|
+
temp.max.x = @max.x.to_f * entity.scale.x + entity.position.x
|
120
|
+
temp.max.y = @max.y.to_f * entity.scale.y + entity.position.y
|
121
|
+
temp.max.z = @max.z.to_f * entity.scale.z + entity.position.z
|
122
|
+
|
123
|
+
temp
|
124
|
+
end
|
125
|
+
|
126
|
+
def +(other)
|
127
|
+
box = BoundingBox.new
|
128
|
+
box.min = min + other.min
|
129
|
+
box.min = max + other.max
|
130
|
+
|
131
|
+
box
|
132
|
+
end
|
133
|
+
|
134
|
+
def -(other)
|
135
|
+
box = BoundingBox.new
|
136
|
+
box.min = min - other.min
|
137
|
+
box.min = max - other.max
|
138
|
+
|
139
|
+
box
|
140
|
+
end
|
141
|
+
|
142
|
+
def sum
|
143
|
+
@min.sum + @max.sum
|
144
|
+
end
|
145
|
+
|
146
|
+
def clone
|
147
|
+
BoundingBox.new(@min.x, @min.y, @min.z, @max.x, @max.y, @max.z)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module CyberarmEngine
|
2
|
+
module Cache
|
3
|
+
class DownloadManager
|
4
|
+
attr_reader :downloads
|
5
|
+
|
6
|
+
def initialize(max_parallel_downloads: 4)
|
7
|
+
@max_parallel_downloads = max_parallel_downloads
|
8
|
+
@downloads = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def download(url:, save_as: nil, &callback)
|
12
|
+
uri = URI(url)
|
13
|
+
save_as ||= "filename_path" # TODO: if no save_as path is provided, then get one from the Cache controller
|
14
|
+
|
15
|
+
@downloads << Download.new(uri: uri, save_as: save_as, callback: callback)
|
16
|
+
end
|
17
|
+
|
18
|
+
def status
|
19
|
+
if active_downloads > 0
|
20
|
+
:busy
|
21
|
+
else
|
22
|
+
:idle
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def progress
|
27
|
+
remaining_bytes = @downloads.map { |d| d.remaining_bytes }.sum
|
28
|
+
total_bytes = @downloads.map { |d| d.total_bytes }.sum
|
29
|
+
|
30
|
+
v = 1.0 - (remaining_bytes.to_f / total_bytes)
|
31
|
+
return 0.0 if v.nan?
|
32
|
+
|
33
|
+
v
|
34
|
+
end
|
35
|
+
|
36
|
+
def active_downloads
|
37
|
+
@downloads.select { |d| %i[pending downloading].include?(d.status) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def update
|
41
|
+
@downloads.each do |download|
|
42
|
+
if download.status == :pending && active_downloads.size <= @max_parallel_downloads
|
43
|
+
download.status = :downloading
|
44
|
+
Thread.start { download.download }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def prune
|
50
|
+
@downloads.delete_if { |d| d.status == :finished || d.status == :failed }
|
51
|
+
end
|
52
|
+
|
53
|
+
class Download
|
54
|
+
attr_accessor :status
|
55
|
+
attr_reader :uri, :save_as, :callback, :remaining_bytes, :total_downloaded_bytes, :total_bytes,
|
56
|
+
:error_message, :started_at, :finished_at
|
57
|
+
|
58
|
+
def initialize(uri:, save_as:, callback: nil)
|
59
|
+
@uri = uri
|
60
|
+
@save_as = save_as
|
61
|
+
@callback = callback
|
62
|
+
|
63
|
+
@status = :pending
|
64
|
+
|
65
|
+
@remaining_bytes = 0.0
|
66
|
+
@total_downloaded_bytes = 0.0
|
67
|
+
@total_bytes = 0.0
|
68
|
+
|
69
|
+
@error_message = ""
|
70
|
+
end
|
71
|
+
|
72
|
+
def progress
|
73
|
+
v = 1.0 - (@remaining_bytes.to_f / total_bytes)
|
74
|
+
return 0.0 if v.nan?
|
75
|
+
|
76
|
+
v
|
77
|
+
end
|
78
|
+
|
79
|
+
def download
|
80
|
+
@status = :downloading
|
81
|
+
@started_at = Time.now # TODO: monotonic time
|
82
|
+
|
83
|
+
io = File.open(@save_as, "w")
|
84
|
+
streamer = lambda do |chunk, remaining_bytes, total_bytes|
|
85
|
+
io.write(chunk)
|
86
|
+
|
87
|
+
@remaining_bytes = remaining_bytes.to_f
|
88
|
+
@total_downloaded_bytes += chunk.size
|
89
|
+
@total_bytes = total_bytes.to_f
|
90
|
+
end
|
91
|
+
|
92
|
+
begin
|
93
|
+
response = Excon.get(
|
94
|
+
@uri.to_s,
|
95
|
+
middlewares: Excon.defaults[:middlewares] + [Excon::Middleware::RedirectFollower],
|
96
|
+
response_block: streamer
|
97
|
+
)
|
98
|
+
|
99
|
+
if response.status == 200
|
100
|
+
@status = :finished
|
101
|
+
@finished_at = Time.now # TODO: monotonic time
|
102
|
+
@callback.call(self) if @callback
|
103
|
+
else
|
104
|
+
@error_message = "Got a non 200 HTTP status of #{response.status}"
|
105
|
+
@status = :failed
|
106
|
+
@finished_at = Time.now # TODO: monotonic time
|
107
|
+
@callback.call(self) if @callback
|
108
|
+
end
|
109
|
+
rescue StandardError => e # TODO: cherrypick errors to cature
|
110
|
+
@status = :failed
|
111
|
+
@finished_at = Time.now # TODO: monotonic time
|
112
|
+
@error_message = e.message
|
113
|
+
@callback.call(self) if @callback
|
114
|
+
end
|
115
|
+
ensure
|
116
|
+
io.close if io
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|