contrek 1.0.2 → 1.0.4
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/Gemfile.lock +16 -8
- data/README.md +65 -3
- data/contrek.gemspec +6 -2
- data/lib/contrek/bitmaps/bitmap.rb +25 -4
- data/lib/contrek/bitmaps/chunky_bitmap.rb +29 -0
- data/lib/contrek/bitmaps/custom_bitmap.rb +9 -0
- data/lib/contrek/bitmaps/png_bitmap.rb +4 -0
- data/lib/contrek/finder/concurrent/clipped_polygon_finder.rb +13 -0
- data/lib/contrek/finder/concurrent/cluster.rb +86 -0
- data/lib/contrek/finder/concurrent/cursor.rb +219 -0
- data/lib/contrek/finder/concurrent/end_point.rb +14 -0
- data/lib/contrek/finder/concurrent/fake_cluster.rb +10 -0
- data/lib/contrek/finder/concurrent/finder.rb +119 -0
- data/lib/contrek/finder/concurrent/hub.rb +12 -0
- data/lib/contrek/finder/concurrent/listable.rb +23 -0
- data/lib/contrek/finder/concurrent/part.rb +72 -0
- data/lib/contrek/finder/concurrent/partitionable.rb +132 -0
- data/lib/contrek/finder/concurrent/polyline.rb +95 -0
- data/lib/contrek/finder/concurrent/poolable.rb +36 -0
- data/lib/contrek/finder/concurrent/position.rb +27 -0
- data/lib/contrek/finder/concurrent/queueable.rb +204 -0
- data/lib/contrek/finder/concurrent/sequence.rb +7 -0
- data/lib/contrek/finder/concurrent/shape.rb +11 -0
- data/lib/contrek/finder/concurrent/tile.rb +91 -0
- data/lib/contrek/finder/node.rb +1 -0
- data/lib/contrek/finder/polygon_finder.rb +18 -5
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +39 -8
- metadata +81 -9
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module Contrek
|
|
2
|
+
module Concurrent
|
|
3
|
+
class Finder
|
|
4
|
+
prepend Poolable
|
|
5
|
+
|
|
6
|
+
attr_reader :maximum_width
|
|
7
|
+
|
|
8
|
+
# Supported options
|
|
9
|
+
# - number_of_threads: number of threads that can be used by the process. If set to 0, it works in
|
|
10
|
+
# single-thread mode.
|
|
11
|
+
# The process can take advantage of multiprocessing when computing the initial contours of the
|
|
12
|
+
# tiles into which the image is divided, and during the subsequent merging of tiles into pairs.
|
|
13
|
+
# Each merge is performed by any available thread as soon as two adjacent tiles have been computed.
|
|
14
|
+
# If set to nil, the process will use the maximum number of cpu available cores.
|
|
15
|
+
# - bitmap: PngBitmap containing the image.
|
|
16
|
+
# - matcher: specific Matcher used for pixel identification.
|
|
17
|
+
# - options: options include:
|
|
18
|
+
# - number_of_tiles: number of tiles into which the image is initially divided.
|
|
19
|
+
# It cannot exceed the image width, so the system works with tiles at least one pixel wide.
|
|
20
|
+
# Given the technique on which the process is based, using excessively narrow tiles is
|
|
21
|
+
# discouraged.
|
|
22
|
+
|
|
23
|
+
def initialize(bitmap:, matcher:, options: {}, &block)
|
|
24
|
+
@initialize_time = Benchmark.measure do
|
|
25
|
+
@block = block
|
|
26
|
+
@tiles = Queue.new
|
|
27
|
+
@bitmap = bitmap
|
|
28
|
+
@options = options
|
|
29
|
+
@clusters = []
|
|
30
|
+
@maximum_width = bitmap.w
|
|
31
|
+
@number_of_tiles = options[:number_of_tiles] || (raise "number_of_tiles params is needed!")
|
|
32
|
+
|
|
33
|
+
cw = @maximum_width.to_f / @number_of_tiles
|
|
34
|
+
raise "One pixel tile width minimum!" if cw < 1.0
|
|
35
|
+
x = 0
|
|
36
|
+
current_versus = options[:versus]
|
|
37
|
+
raise "Define versus!" if current_versus.nil?
|
|
38
|
+
|
|
39
|
+
@number_of_tiles.times do |tile_index|
|
|
40
|
+
tile_end_x = (cw * (tile_index + 1)).to_i
|
|
41
|
+
|
|
42
|
+
enqueue!(tile_index: tile_index,
|
|
43
|
+
tile_start_x: x,
|
|
44
|
+
tile_end_x: tile_end_x) do |payload|
|
|
45
|
+
finder = ClippedPolygonFinder.new(
|
|
46
|
+
bitmap: bitmap,
|
|
47
|
+
matcher: matcher,
|
|
48
|
+
options: {versus: current_versus},
|
|
49
|
+
start_x: payload[:tile_start_x],
|
|
50
|
+
end_x: payload[:tile_end_x]
|
|
51
|
+
)
|
|
52
|
+
tile = Tile.new(
|
|
53
|
+
finder: self,
|
|
54
|
+
start_x: payload[:tile_start_x],
|
|
55
|
+
end_x: payload[:tile_end_x],
|
|
56
|
+
name: payload[:tile_index].to_s
|
|
57
|
+
)
|
|
58
|
+
tile.initial_process!(finder)
|
|
59
|
+
@tiles << tile
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
x = tile_end_x - 1
|
|
63
|
+
end
|
|
64
|
+
@tile = process_tiles!(bitmap)
|
|
65
|
+
end.real
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def process_info(bitmap = nil)
|
|
69
|
+
raw_polygons = @tile.to_raw_polygons
|
|
70
|
+
|
|
71
|
+
compress_time = Benchmark.measure do
|
|
72
|
+
if @options.has_key?(:compress)
|
|
73
|
+
FakeCluster.new(raw_polygons, @options).compress_coords
|
|
74
|
+
end
|
|
75
|
+
end.real
|
|
76
|
+
|
|
77
|
+
{polygons: raw_polygons,
|
|
78
|
+
benchmarks: {
|
|
79
|
+
total: ((@initialize_time + compress_time) * 1000).round(3),
|
|
80
|
+
init: (@initialize_time * 1000).round(3),
|
|
81
|
+
outer: (@tile.benchmarks[:outer] * 1000).round(3),
|
|
82
|
+
inner: (@tile.benchmarks[:inner] * 1000).round(3),
|
|
83
|
+
compress: ((compress_time * 1000).round(3) if @options.has_key?(:compress))
|
|
84
|
+
}.compact}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def process_tiles!(bitmap)
|
|
90
|
+
arriving_tiles = []
|
|
91
|
+
loop do
|
|
92
|
+
tile = @tiles.pop
|
|
93
|
+
return tile if tile.whole?
|
|
94
|
+
|
|
95
|
+
if (twin_tile = arriving_tiles.find { |b| (b.start_x == (tile.end_x - 1)) || ((b.end_x - 1) == tile.start_x) })
|
|
96
|
+
cluster = Cluster.new(finder: self, height: bitmap.h, width: bitmap.w)
|
|
97
|
+
if twin_tile.start_x == (tile.end_x - 1)
|
|
98
|
+
cluster.add(tile)
|
|
99
|
+
cluster.add(twin_tile)
|
|
100
|
+
else
|
|
101
|
+
cluster.add(twin_tile)
|
|
102
|
+
cluster.add(tile)
|
|
103
|
+
end
|
|
104
|
+
enqueue!(cluster: cluster) do |payload|
|
|
105
|
+
merged_tile = payload[:cluster].merge_tiles!
|
|
106
|
+
@tiles << merged_tile
|
|
107
|
+
# usefull external access to each merged_tile
|
|
108
|
+
@block&.call(merged_tile, bitmap)
|
|
109
|
+
end
|
|
110
|
+
arriving_tiles.delete(twin_tile)
|
|
111
|
+
next
|
|
112
|
+
end
|
|
113
|
+
arriving_tiles << tile
|
|
114
|
+
end
|
|
115
|
+
nil
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Contrek
|
|
2
|
+
module Concurrent
|
|
3
|
+
module Listable
|
|
4
|
+
attr_accessor :prev, :next, :owner
|
|
5
|
+
def initialize(*args, **kwargs, &block)
|
|
6
|
+
super
|
|
7
|
+
@next = nil
|
|
8
|
+
@prev = nil
|
|
9
|
+
@owner = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def payload
|
|
13
|
+
raise NoMethodError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def after_add(new_queue)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def before_rem(old_queue)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Contrek
|
|
2
|
+
module Concurrent
|
|
3
|
+
class Part
|
|
4
|
+
prepend Queueable
|
|
5
|
+
|
|
6
|
+
SEAM = 1
|
|
7
|
+
EXCLUSIVE = 0
|
|
8
|
+
ADDED = 2
|
|
9
|
+
|
|
10
|
+
attr_reader :polyline, :index, :touched
|
|
11
|
+
attr_accessor :next, :circular_next, :prev, :type, :passes, :inverts, :trasmuted
|
|
12
|
+
def initialize(type, polyline)
|
|
13
|
+
@type = type
|
|
14
|
+
@polyline = polyline
|
|
15
|
+
@next = nil
|
|
16
|
+
@circular_next = nil
|
|
17
|
+
@prev = nil
|
|
18
|
+
@passes = 0
|
|
19
|
+
@touched = false
|
|
20
|
+
@inverts = false
|
|
21
|
+
@trasmuted = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def is?(type)
|
|
25
|
+
@type == type
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def add_position(position, n)
|
|
29
|
+
add(Position.new(position: position, hub: polyline.tile.cluster.hub))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def next_position(force_position = nil)
|
|
33
|
+
if force_position
|
|
34
|
+
move_to_this = reverse_each { |pos| break pos if pos.payload == force_position }
|
|
35
|
+
next_of!(move_to_this)
|
|
36
|
+
force_position
|
|
37
|
+
else
|
|
38
|
+
return nil if iterator.nil?
|
|
39
|
+
position = iterator
|
|
40
|
+
@touched = true
|
|
41
|
+
forward!
|
|
42
|
+
position
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def touch!
|
|
47
|
+
@touched = true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def name
|
|
51
|
+
{Part::EXCLUSIVE => "EXCLUSIVE",
|
|
52
|
+
Part::SEAM => "SEAM",
|
|
53
|
+
Part::ADDED => "ADDED"}[type]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def inspect
|
|
57
|
+
"part #{polyline.parts.index(self)} (inv=#{@inverts} trm=#{@trasmuted} touched=#{@touched} passes=#{@passes}, #{size}x) of #{polyline.info} (#{name}) (#{to_a.map { |e| "[#{e[:x]},#{e[:y]}]" }.join})"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def innerable?
|
|
61
|
+
(@touched == false) && is?(EXCLUSIVE)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def intersect_part?(other_part)
|
|
65
|
+
other_part.each do |position|
|
|
66
|
+
return true if position.end_point.queues.include?(self)
|
|
67
|
+
end
|
|
68
|
+
false
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
module Contrek
|
|
2
|
+
module Concurrent
|
|
3
|
+
module Partitionable
|
|
4
|
+
attr_reader :parts
|
|
5
|
+
|
|
6
|
+
def initialize(*args, **kwargs, &block)
|
|
7
|
+
super
|
|
8
|
+
@parts = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_part(new_part)
|
|
12
|
+
last = @parts.last
|
|
13
|
+
@parts << new_part
|
|
14
|
+
last.next = last.circular_next = new_part if last
|
|
15
|
+
new_part.circular_next = @parts.first
|
|
16
|
+
new_part.prev = last
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def find_first_part_by_position(position)
|
|
20
|
+
@parts.find do |part|
|
|
21
|
+
part.is?(Part::SEAM) &&
|
|
22
|
+
part.passes == 0 &&
|
|
23
|
+
position.end_point.queues.include?(part)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def inspect_parts
|
|
28
|
+
[" "] + ["#{self.class} parts=#{@parts.size}"] + @parts.map { |p| p.inspect } + [" "]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def partition!
|
|
32
|
+
current_part = nil
|
|
33
|
+
@parts = []
|
|
34
|
+
|
|
35
|
+
@raw.each_with_index do |position, n|
|
|
36
|
+
if @tile.tg_border?(position)
|
|
37
|
+
if current_part.nil?
|
|
38
|
+
current_part = Part.new(Part::SEAM, self)
|
|
39
|
+
elsif !current_part.is?(Part::SEAM)
|
|
40
|
+
add_part(current_part)
|
|
41
|
+
current_part = Part.new(Part::SEAM, self)
|
|
42
|
+
end
|
|
43
|
+
elsif current_part.nil?
|
|
44
|
+
current_part = Part.new(Part::EXCLUSIVE, self)
|
|
45
|
+
elsif !current_part.is?(Part::EXCLUSIVE)
|
|
46
|
+
add_part(current_part)
|
|
47
|
+
current_part = Part.new(Part::EXCLUSIVE, self)
|
|
48
|
+
end
|
|
49
|
+
if n > 0 && @raw[n - 1] == position
|
|
50
|
+
current_part.inverts = true
|
|
51
|
+
end
|
|
52
|
+
current_part.add_position(position, n)
|
|
53
|
+
end
|
|
54
|
+
add_part(current_part)
|
|
55
|
+
|
|
56
|
+
trasmute_parts!
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def sew!(intersection, other)
|
|
60
|
+
matching_part_indexes = []
|
|
61
|
+
parts.each_with_index do |part, index|
|
|
62
|
+
next if part.trasmuted
|
|
63
|
+
matching_part_indexes << index if part.intersection_with_array?(intersection)
|
|
64
|
+
end
|
|
65
|
+
other_matching_part_indexes = []
|
|
66
|
+
other.parts.each_with_index do |part, index|
|
|
67
|
+
next if part.trasmuted
|
|
68
|
+
other_matching_part_indexes << index if part.intersection_with_array?(intersection)
|
|
69
|
+
end
|
|
70
|
+
# other_matching_part_indexes and matching_part_indexes always contain at least one element
|
|
71
|
+
before_parts = other.parts[other_matching_part_indexes.last + 1..]
|
|
72
|
+
after_parts = other_matching_part_indexes.first.zero? ? [] : other.parts[0..other_matching_part_indexes.first - 1]
|
|
73
|
+
part_start = parts[matching_part_indexes.first]
|
|
74
|
+
part_end = parts[matching_part_indexes.last]
|
|
75
|
+
|
|
76
|
+
# They are inverted since they traverse in opposite directions
|
|
77
|
+
sequence = Sequence.new
|
|
78
|
+
sequence.add part_start.head
|
|
79
|
+
before_parts.each { |part| sequence.append(part) }
|
|
80
|
+
after_parts.each { |part| sequence.append(part) }
|
|
81
|
+
sequence.add part_end.tail if part_end.tail # nil when part_start == part_end
|
|
82
|
+
|
|
83
|
+
part_start.replace!(sequence)
|
|
84
|
+
part_start.type = Part::EXCLUSIVE
|
|
85
|
+
part_end.reset! if part_start != part_end
|
|
86
|
+
|
|
87
|
+
left = []
|
|
88
|
+
(matching_part_indexes.first + 1).upto(matching_part_indexes.last - 1) do |n|
|
|
89
|
+
left << parts[n].to_a if matching_part_indexes.index(n).nil?
|
|
90
|
+
end
|
|
91
|
+
(matching_part_indexes.last - 1).downto(matching_part_indexes.first + 1) do |n|
|
|
92
|
+
delete_part = parts[n]
|
|
93
|
+
delete_part.prev.next = delete_part.next if delete_part.prev
|
|
94
|
+
delete_part.next.prev = delete_part.prev if delete_part.next
|
|
95
|
+
parts.delete_at(n)
|
|
96
|
+
end
|
|
97
|
+
right = []
|
|
98
|
+
(other_matching_part_indexes.first + 1).upto(other_matching_part_indexes.last - 1) do |n|
|
|
99
|
+
right << other.parts[n].to_a if other_matching_part_indexes.index(n).nil?
|
|
100
|
+
end
|
|
101
|
+
[left, right]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
# If there are SEAM parts and one is canceled out by another within the same polyline,
|
|
107
|
+
# meaning that all its points are repeated in another, longer sequence,
|
|
108
|
+
# then the shorter one is converted to EXCLUSIVE and marked as transmuted
|
|
109
|
+
def trasmute_parts!
|
|
110
|
+
insides = @parts.select { |p| p.is?(Part::SEAM) }
|
|
111
|
+
return if insides.size < 2
|
|
112
|
+
|
|
113
|
+
insides.each do |inside|
|
|
114
|
+
(insides - [inside]).each do |inside_compare|
|
|
115
|
+
next unless inside_compare.is?(Part::SEAM)
|
|
116
|
+
|
|
117
|
+
count = 0
|
|
118
|
+
inside.each do |position|
|
|
119
|
+
break unless position.end_point.queues.include?(inside_compare)
|
|
120
|
+
count += 1
|
|
121
|
+
end
|
|
122
|
+
if count == inside.size
|
|
123
|
+
inside.type = Part::EXCLUSIVE
|
|
124
|
+
inside.trasmuted = true
|
|
125
|
+
break
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Contrek
|
|
2
|
+
module Concurrent
|
|
3
|
+
class Polyline
|
|
4
|
+
prepend Partitionable
|
|
5
|
+
|
|
6
|
+
TRACKED_OUTER = 1 << 0
|
|
7
|
+
TRACKED_INNER = 1 << 1
|
|
8
|
+
Bounds = Struct.new(:min_x, :max_x, :min_y, :max_y)
|
|
9
|
+
|
|
10
|
+
attr_reader :raw, :name, :min_y, :max_y, :next_tile_eligible_shapes
|
|
11
|
+
attr_accessor :shape, :tile
|
|
12
|
+
|
|
13
|
+
def initialize(tile:, polygon:, shape: nil)
|
|
14
|
+
@tile = tile
|
|
15
|
+
@name = tile.shapes.count
|
|
16
|
+
@raw = polygon
|
|
17
|
+
@shape = shape
|
|
18
|
+
@flags = 0
|
|
19
|
+
find_boundary
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def inspect
|
|
23
|
+
"#{self.class}[b#{@tile.name} S#{@name} #{"B" if boundary?}] (#{raw.count} => #{raw.inspect})"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def info
|
|
27
|
+
"w#{@tile.name} S#{@name}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def turn_on(flag)
|
|
31
|
+
@flags |= flag
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def turn_off(flag)
|
|
35
|
+
@flags &= ~flag
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def on?(flag)
|
|
39
|
+
(@flags & flag) != 0
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def intersection(other)
|
|
43
|
+
(@raw.compact & other.raw.compact)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def empty?
|
|
47
|
+
@raw.empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def boundary?
|
|
51
|
+
@tile.tg_border?({x: @min_x}) || @tile.tg_border?({x: @max_x})
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def clear!
|
|
55
|
+
@raw = []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def vert_intersect?(other)
|
|
59
|
+
!(@max_y < other.min_y || other.max_y < @min_y)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def width
|
|
63
|
+
return 0 if empty?
|
|
64
|
+
@max_x - @min_x
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Pre-detects, for the current polyline, adjacent ones in the neighboring tile
|
|
68
|
+
# that vertically intersect.
|
|
69
|
+
def precalc!
|
|
70
|
+
@next_tile_eligible_shapes = @tile
|
|
71
|
+
.circular_next.boundary_shapes
|
|
72
|
+
.select { |s|
|
|
73
|
+
!s.outer_polyline.on?(Polyline::TRACKED_OUTER) &&
|
|
74
|
+
vert_intersect?(s.outer_polyline)
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def find_boundary
|
|
81
|
+
return if @raw.empty?
|
|
82
|
+
|
|
83
|
+
bounds = @raw.compact.each_with_object(Bounds.new(Float::INFINITY, -Float::INFINITY, Float::INFINITY, -Float::INFINITY)) do |c, b|
|
|
84
|
+
x, y = c[:x], c[:y]
|
|
85
|
+
b.min_x = x if x < b.min_x
|
|
86
|
+
b.max_x = x if x > b.max_x
|
|
87
|
+
b.min_y = y if y < b.min_y
|
|
88
|
+
b.max_y = y if y > b.max_y
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
@min_x, @max_x, @min_y, @max_y = bounds.values
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require "concurrent-ruby"
|
|
2
|
+
|
|
3
|
+
module Contrek
|
|
4
|
+
module Concurrent
|
|
5
|
+
module Poolable
|
|
6
|
+
attr_reader :number_of_threads
|
|
7
|
+
def initialize(number_of_threads: 0, **kwargs)
|
|
8
|
+
@number_of_threads = number_of_threads || ::Concurrent.physical_processor_count
|
|
9
|
+
if @number_of_threads > 0
|
|
10
|
+
@threads = ::Concurrent::Array.new
|
|
11
|
+
@semaphore = ::Concurrent::Semaphore.new(@number_of_threads)
|
|
12
|
+
end
|
|
13
|
+
super(**kwargs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def wait!
|
|
17
|
+
@threads.each(&:join)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def enqueue!(**payload, &block)
|
|
21
|
+
if @number_of_threads > 0
|
|
22
|
+
@threads << Thread.new do
|
|
23
|
+
@semaphore.acquire
|
|
24
|
+
begin
|
|
25
|
+
block.call(payload)
|
|
26
|
+
ensure
|
|
27
|
+
@semaphore.release
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
block.call(payload)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Contrek
|
|
2
|
+
module Concurrent
|
|
3
|
+
class Position
|
|
4
|
+
include Listable
|
|
5
|
+
|
|
6
|
+
attr_reader :end_point
|
|
7
|
+
|
|
8
|
+
def initialize(hub:, position:)
|
|
9
|
+
key = position[:y] * hub.width + position[:x]
|
|
10
|
+
@end_point = hub.payloads[key] ||= EndPoint.new
|
|
11
|
+
@position = position
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def payload
|
|
15
|
+
@position
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def after_add(new_queue)
|
|
19
|
+
@end_point.queues << new_queue
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def before_rem(old_queue)
|
|
23
|
+
@end_point.queues.delete(old_queue)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|