smcty 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 066404cac54ab6ea59d05f19655509a73fe25879
4
+ data.tar.gz: 9fb49f6ed6a8d580f4f63d8558ce012b5ebc1fab
5
+ SHA512:
6
+ metadata.gz: 79c2493d00915a80980b58991bd7f13a38fdb5f0fd5ebb79884c29f09cbcb584d359824b01085a5a7488a1a458c5bd5d27659f130cae8cda89751e0d13a9e969
7
+ data.tar.gz: dde05154fcd122b9f13c0907716bfb8f0cebfdcad6b4a8756bcdf8238ab3407deb760266b556bcbb20eabb848fa1504b4bc075ad38ac8c294c05a011abd7e5a3
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'smcty'
4
+ Smcty::Planner.start(ARGV[0])
@@ -0,0 +1 @@
1
+ require 'smcty/planner'
@@ -0,0 +1,26 @@
1
+ module Smcty
2
+ class Allocation
3
+ attr_reader :store, :resource, :amount
4
+
5
+ def initialize(store, resource, amount)
6
+ @store = store
7
+ @resource = resource
8
+ @amount = amount
9
+ end
10
+
11
+ def valid?
12
+ @store.valid?(self)
13
+ end
14
+
15
+ def get
16
+ @store.get(self)
17
+ end
18
+
19
+ def to_hash
20
+ {
21
+ "resource" => @resource.name,
22
+ "amount" => @amount
23
+ }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,61 @@
1
+ module Smcty
2
+ class Configuration
3
+ attr_reader :created_at, :store, :scheduler
4
+
5
+ def initialize(store)
6
+ @created_at = Time.new
7
+ @store = store
8
+ @factories = {}
9
+ @scheduler = Scheduling.new(self)
10
+ end
11
+
12
+ def register_factory(factory)
13
+ @factories[factory.name] = factory
14
+ end
15
+
16
+ def resource_by_name(resource_name)
17
+ @factories.values.each do |factory|
18
+ resource = factory.resource_by_name(resource_name)
19
+ return resource if resource
20
+ end
21
+ nil
22
+ end
23
+
24
+ def resources
25
+ result = []
26
+ @factories.values.each do |f|
27
+ result += f.resources
28
+ end
29
+ result
30
+ end
31
+
32
+ def factory_for(resource)
33
+ @factories.values.each do |factory|
34
+ found = factory.resource_by_name(resource.name)
35
+ return factory if found
36
+ end
37
+ nil
38
+ end
39
+
40
+ def factories
41
+ @factories.keys.sort
42
+ end
43
+
44
+ def factory(name)
45
+ @factories[name]
46
+ end
47
+
48
+ def to_s
49
+ "Configuration created at #{@created_at}"
50
+ end
51
+
52
+ def to_hash
53
+ {
54
+ "store" => @store.to_hash,
55
+ "factories" => @factories.values.map{|f| f.to_hash},
56
+ "scheduling" => @scheduler.to_hash
57
+ }
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,178 @@
1
+ require 'json'
2
+
3
+ require 'smcty/store'
4
+ require 'smcty/configuration'
5
+ require 'smcty/factory'
6
+ require 'smcty/resource'
7
+
8
+ module Smcty
9
+ class Configurator
10
+ attr_reader :configuration
11
+
12
+ def initialize(config_path)
13
+ @config_path = config_path
14
+ @configuration = Configurator.read_configuration(config_path)
15
+ end
16
+
17
+ def save
18
+ Configurator.write_configuration(@configuration, @config_path)
19
+ end
20
+
21
+ def self.read_configuration(path)
22
+ file = File.read(path)
23
+ data_hash = JSON.parse(file)
24
+
25
+ if (data_hash && data_hash != {})
26
+ store_hash = read_value(data_hash, "store", true)
27
+ store_name = read_value(store_hash, "name", true)
28
+ store_capacity = read_value(store_hash, "capacity", true)
29
+ store = Store.new(store_name, store_capacity)
30
+
31
+ configuration = Configuration.new(store)
32
+
33
+ deferred_dependencies = []
34
+
35
+ factories_list = read_value(data_hash, "factories", true)
36
+ factories_list.each do |factory_hash|
37
+ factory_name = read_value(factory_hash, "name", true)
38
+ factory_capacity = read_value(factory_hash, "capacity", true)
39
+ factory_sequential = read_value(factory_hash, "sequential", false, false)
40
+
41
+ factory = Factory.new(factory_name, factory_capacity, factory_sequential)
42
+ configuration.register_factory(factory)
43
+
44
+ factory_resources = read_value(factory_hash, "resources", true)
45
+ factory_resources.each do |resources_hash|
46
+ resource_name = read_value(resources_hash, "name", true)
47
+ resource_description = read_value(resources_hash, "description", true)
48
+ resource_time = read_value(resources_hash, "time", true)
49
+
50
+ resource = Resource.new(resource_name, resource_description)
51
+ factory.register_resource(resource, resource_time)
52
+
53
+ resource_deps = read_value(resources_hash, "deps", false, [])
54
+ resource_deps.each do |dependency_hash|
55
+ dependency_name = read_value(dependency_hash, "name", true)
56
+ dependency_amount = read_value(dependency_hash, "amount", true)
57
+
58
+ if dep_resource = configuration.resource_by_name(dependency_name)
59
+ resource.register_dependency(dep_resource, dependency_amount)
60
+ else
61
+ deferred_dependencies << {resource: resource,
62
+ dependency_name: dependency_name,
63
+ dependency_amount: dependency_amount
64
+ }
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ # post process deferred dependencies
71
+ deferred_dependencies.each do |deferred_dep|
72
+ resource = deferred_dep[:resource]
73
+ dependency_name = deferred_dep[:dependency_name]
74
+ amount = deferred_dep[:dependency_amount]
75
+
76
+ dependent_resource = configuration.resource_by_name(dependency_name)
77
+ unless dependent_resource
78
+ raise "Cannot fullfill dependency from #{resource.name} -> #{dependency_name}"
79
+ end
80
+
81
+ resource.register_dependency(dependent_resource, amount)
82
+ end
83
+
84
+ # post process stock of store
85
+ read_value(store_hash, "stock", false, []).each do |stock_hash|
86
+ resource_name = read_value(stock_hash, "name", true)
87
+ resource_amount = read_value(stock_hash, "amount", true)
88
+
89
+ resource = configuration.resource_by_name(resource_name) || Resource.new(resource_name, "unmanaged")
90
+ store.put(resource, resource_amount)
91
+ end
92
+
93
+ # post process the scheduling information
94
+ scheduling_hash = read_value(data_hash, "scheduling", true)
95
+ read_value(scheduling_hash, "projects", false, []).each do |project_hash|
96
+ project_name = read_value(project_hash, "name", true)
97
+ project = Project.new(project_name)
98
+ read_value(project_hash, "requirements", true).each do |req|
99
+ resource_name = read_value(req, "resource", true)
100
+ amount = read_value(req, "amount", true).to_i
101
+ resource = configuration.resource_by_name(resource_name)
102
+ unless resource
103
+ raise "Cannot register requirement for unknown resource #{resource_name}"
104
+ end
105
+ project.add_requirement(resource, amount)
106
+ end
107
+
108
+ job_dict = {}
109
+ job_dependencies = {}
110
+ jobs = []
111
+
112
+ # first pass to create the single jobs
113
+ read_value(project_hash, "jobs", true).each do |job_hash|
114
+ reference = read_value(job_hash, "id", true)
115
+ resource_name = read_value(job_hash, "resource", true)
116
+ resource = configuration.resource_by_name(resource_name)
117
+ unless resource
118
+ raise "Cannot create job for unknown resource #{resource_name}"
119
+ end
120
+ job = Job.new(resource)
121
+ job_dict[reference] = job
122
+ jobs << job
123
+
124
+ allocation_hash = read_value(job_hash, "allocation", false, nil)
125
+ if allocation_hash
126
+ amount = read_value(allocation_hash, "amount", true).to_i
127
+ allocation = store.allocate(resource, amount)
128
+ job.allocate(allocation) if allocation
129
+ end
130
+
131
+ production_hash = read_value(job_hash, "production", false, nil)
132
+ if production_hash
133
+ start_time = read_value(production_hash, "start_time", true).to_i
134
+ duration = read_value(production_hash, "duration", true).to_i
135
+ production = Production.new(resource, duration, start_time)
136
+ job.produce(production)
137
+ end
138
+
139
+ dependency_list = read_value(job_hash, "dependent_jobs", false, nil)
140
+ job_dependencies[job] = dependency_list if dependency_list
141
+ end
142
+
143
+ # second pass to connect dependent jobs
144
+ job_dependencies.keys.each do |job|
145
+ job_dependencies[job].each do |dep_ref|
146
+ dep_job = job_dict[dep_ref]
147
+ unless dep_job
148
+ raise "job references another job which is not known (#{dep_ref})"
149
+ end
150
+ job.add_dependent(dep_job)
151
+ end
152
+ end
153
+
154
+ configuration.scheduler.load_project(project, jobs)
155
+ end
156
+
157
+ configuration
158
+ end
159
+ end
160
+
161
+ def self.write_configuration(configuration, path)
162
+ File.open(path,"w") do |f|
163
+ f.write(configuration.to_hash.to_json)
164
+ end
165
+ end
166
+
167
+ private
168
+
169
+ def self.read_value(hash, key, force, default=nil)
170
+ value = hash[key]
171
+ if force && (value == nil)
172
+ raise "The hash #{hash} does not contain the key #{key}"
173
+ end
174
+ value || default
175
+ end
176
+
177
+ end
178
+ end
@@ -0,0 +1,229 @@
1
+ require 'smcty/configurator'
2
+ require 'smcty/output'
3
+ require 'smcty/helpers'
4
+ require 'smcty/production'
5
+ require 'smcty/project'
6
+ require 'smcty/scheduling'
7
+
8
+ module Smcty
9
+ class Console
10
+
11
+ # Initialize the console with the input stream (e.g. $stdin)
12
+ # and a path to smcty configuration file.
13
+ def initialize(input_stream, config_path)
14
+ @in = input_stream
15
+ @configurator = Configurator.new(config_path)
16
+ @configuration = @configurator.configuration
17
+ @productions = {}
18
+
19
+ end
20
+
21
+ def prompt!
22
+ run = true
23
+ while run
24
+ "-> ".display
25
+ run = dispatch(@in.gets.chomp)
26
+ end
27
+ end
28
+
29
+ # Commands
30
+ #
31
+ # quit
32
+ #
33
+ # store -> list the inventory of the store
34
+ # store put #item #amount
35
+ #
36
+ # factory -> list the registered factories
37
+ # factory #name -> list the resources and the production of the named factory
38
+ #
39
+ # resources -> list all managed resources
40
+ #
41
+ # project add #label [#resource:#amount]+
42
+ # project #label
43
+ #
44
+ # produce #resource
45
+ #
46
+ # pick #production-number
47
+ #
48
+ # next
49
+ #
50
+ def dispatch(input)
51
+ continue = true
52
+ commands = input.split(" ")
53
+ case commands[0].downcase
54
+ when "store"
55
+ continue = process_store(commands[1..-1])
56
+ when "factory"
57
+ continue = process_factory(commands[1..-1])
58
+ when "quit"
59
+ continue = process_quit
60
+ when "resources"
61
+ continue = process_resources
62
+ when "project"
63
+ continue = process_projects(commands[1..-1])
64
+ when "produce"
65
+ continue = process_production(commands[1..-1])
66
+ when "pick"
67
+ continue = process_pick(commands[1..-1])
68
+ when "next"
69
+ continue = process_next
70
+ when "save"
71
+ continue = process_save
72
+ when "scheduling"
73
+ continue = process_scheduling
74
+ else
75
+ puts "unknown command: #{input}"
76
+ end
77
+ return continue
78
+ end
79
+
80
+ def process_scheduling
81
+ @configuration.scheduler.print_job_lists
82
+ true
83
+ end
84
+
85
+ def process_next
86
+ puts "DO > #{@configuration.scheduler.next}"
87
+ true
88
+ end
89
+
90
+ def process_projects(commands)
91
+ if commands.size == 1
92
+ puts "print the project"
93
+ elsif commands.size > 2
94
+ unless commands[0] == "add"
95
+ puts "unknow command #{commands[0]} for project is not known"
96
+ return true
97
+ end
98
+ project = Project.new(commands[1])
99
+ requirements = commands[2..-1]
100
+ requirements.each do |r|
101
+ items = r.split(":")
102
+ r_name = items[0]
103
+ amount = items[1].to_i
104
+ resource = @configuration.resource_by_name(r_name)
105
+ unless resource
106
+ puts "the resource #{resource_name} is not known"
107
+ return true
108
+ end
109
+ project.add_requirement(resource, amount)
110
+ end
111
+ @configuration.scheduler.plan_project(project)
112
+ puts "planned the project"
113
+ else
114
+ puts "not enough parameters for project command"
115
+ end
116
+ true
117
+ end
118
+
119
+ def process_pick(commands)
120
+ if commands.size != 1
121
+ puts "please pass the number of production to pick"
122
+ else
123
+ number = commands[0].to_i
124
+ production = @productions[number]
125
+ unless production
126
+ puts "the production number #{commands[0]} is not known"
127
+ return true
128
+ end
129
+ unless production.finished?
130
+ puts "the production is not finished yet"
131
+ return true
132
+ end
133
+ if @configuration.store.free_capacity > 0
134
+ factory = @configuration.factory_for(production.resource)
135
+ factory.pick(production)
136
+ @productions.delete(number)
137
+ @configuration.store.put(production.resource, 1)
138
+ puts "the item was stored"
139
+ else
140
+ puts "no storage left"
141
+ end
142
+ end
143
+ true
144
+ end
145
+
146
+ def process_production(commands)
147
+ if commands.size != 1
148
+ puts "please name the resource to produce"
149
+ else
150
+ resource_name = commands[0]
151
+ resource = @configuration.resource_by_name(resource_name)
152
+ unless resource
153
+ puts "the resource #{resource_name} is not known"
154
+ return true
155
+ end
156
+ factory = @configuration.factory_for(resource)
157
+ allocations = []
158
+ resource.dependent_resources.each do |r|
159
+ allocation = @configuration.store.allocate(r, resource.dependent_resource_amount(r))
160
+ unless allocation
161
+ puts "Not enough of #{r.name} to produce #{resource_name}"
162
+ return true
163
+ end
164
+ allocations << allocation
165
+ end
166
+ production = factory.produce(resource, allocations)
167
+ @productions[production.object_id] = production
168
+ puts "Now producing: #{production.object_id} in factory: #{factory.name}"
169
+ end
170
+ true
171
+ end
172
+
173
+ def process_resources
174
+ list_resources(@configuration.resources)
175
+ true
176
+ end
177
+
178
+ def process_quit
179
+ puts "Storing configuration back to file and exit."
180
+ @configurator.save
181
+ false
182
+ end
183
+
184
+ def process_save
185
+ puts "Storing configuration back to file"
186
+ @configurator.save
187
+ true
188
+ end
189
+
190
+ def process_store(commands)
191
+ if commands.size == 0
192
+ list_inventory(@configuration.store)
193
+ elsif commands.size == 3
194
+ resource = @configuration.resource_by_name(commands[1])
195
+ amount = commands[2].to_i
196
+ if resource && amount > 0
197
+ case commands[0].downcase
198
+ when "put"
199
+ puts "put #{commands[2]} units of #{commands[1]} to store"
200
+ result = @configuration.store.put(resource, amount)
201
+ puts "new stock is: #{@configuration.store.stock(resource)}"
202
+ else
203
+ puts "operator on store was not recognized"
204
+ end
205
+ else
206
+ puts "resource '#{commands[1]}' or amount '#{commands[2]}' not valid"
207
+ end
208
+ else
209
+ puts "command not recognized"
210
+ end
211
+ true
212
+ end
213
+
214
+ def process_factory(commands)
215
+ if commands.size == 0
216
+ list_factories(@configuration.factories)
217
+ else
218
+ factory = @configuration.factory(commands[0])
219
+ if factory
220
+ list_factory(factory)
221
+ else
222
+ puts "The factory '#{commands[0]}' is not registered"
223
+ end
224
+ end
225
+ true
226
+ end
227
+
228
+ end
229
+ end
@@ -0,0 +1,105 @@
1
+ module Smcty
2
+ class Factory
3
+ attr_reader :name, :capacity, :productions, :sequential
4
+
5
+ def initialize(name, capacity, sequential=false)
6
+ @name = name
7
+ @capacity = capacity
8
+ @resources = {}
9
+ @production_times = {}
10
+ @productions = []
11
+ @sequential = sequential
12
+ end
13
+
14
+ def free_capacity
15
+ capacity - @productions.size
16
+ end
17
+
18
+ def register_resource(resource, production_time)
19
+ @production_times[resource] = production_time
20
+ @resources[resource.name] = resource
21
+ end
22
+
23
+ def resources
24
+ @production_times.keys
25
+ end
26
+
27
+ def production_time(resource)
28
+ @production_times[resource]
29
+ end
30
+
31
+ def resource_by_name(name)
32
+ @resources[name]
33
+ end
34
+
35
+ def produce(resource, allocations=[])
36
+ unless free_capacity > 0
37
+ raise "The factory has no capacity for further production"
38
+ end
39
+
40
+ unless Factory.check_preconditions(resource, allocations)
41
+ raise "The preconditions for this production aren't met"
42
+ end
43
+
44
+ time = @production_times[resource]
45
+ unless time
46
+ raise "The resource #{resource.name} is not registered with this factory"
47
+ end
48
+
49
+ # in case of a sequential production the time must be extended
50
+ # find latest end time of a preceeding production
51
+ if sequential && latest = @productions.sort{|x,y| y.end_time <=> x.end_time}.first
52
+ time += (latest.end_time - Time.now).to_i
53
+ end
54
+
55
+ allocations.each{ |a| a.get }
56
+ production = Production.new(resource, time)
57
+ @productions << production
58
+ production
59
+ end
60
+
61
+ def pick(production)
62
+ if production && production.finished?
63
+ @productions.delete(production)
64
+ end
65
+ end
66
+
67
+ def to_s
68
+ "Factory '#{@name}' with capacity of #{@capacity} is responsible for #{@resources.keys.join(", ")}"
69
+ end
70
+
71
+ def to_hash
72
+ {
73
+ "name" => @name,
74
+ "capacity" => @capacity,
75
+ "sequential" => @sequential,
76
+ "resources" => @resources.values.map{|r| r.to_hash(@production_times[r])}
77
+ }
78
+ end
79
+
80
+ private
81
+
82
+ def self.check_preconditions(resource, allocations)
83
+ allocated_resources = {}
84
+ allocations.each do |a|
85
+ if a.valid?
86
+ depot = allocated_resources[a.resource] || {amount: 0}
87
+ depot[:amount] += a.amount
88
+ allocated_resources[a.resource] = depot
89
+ end
90
+ end
91
+
92
+ resource.dependent_resources.each do |dr|
93
+ required_amount = resource.dependent_resource_amount(dr)
94
+ depot = allocated_resources[dr]
95
+ if depot && depot[:amount] >= required_amount
96
+ depot[:amount] -= required_amount
97
+ else
98
+ return false
99
+ end
100
+ end
101
+ return true
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,16 @@
1
+ def natural_time(time)
2
+ unit = time
3
+
4
+ # given unit in seconds
5
+ hours = unit / 3600
6
+ unit = unit - (hours * 3600)
7
+
8
+ minutes = unit / 60
9
+ seconds = unit - (minutes * 60)
10
+
11
+ result = (hours > 0) ? "#{hours}h " : ""
12
+ result += (minutes > 0) ? "#{minutes}m " : ""
13
+ result += "#{seconds}s"
14
+
15
+ result.strip
16
+ end
@@ -0,0 +1,72 @@
1
+ module Smcty
2
+ class Job
3
+ attr_reader :resource, :allocation, :production, :dependent_jobs
4
+
5
+ def initialize(resource)
6
+ @resource = resource
7
+ @used = false
8
+ @dependent_jobs = []
9
+ end
10
+
11
+ def allocate(allocation)
12
+ @allocation = allocation
13
+ @production = nil
14
+ end
15
+
16
+ def produce(production)
17
+ @allocation = nil
18
+ @production = production
19
+ end
20
+
21
+ def add_dependent(job)
22
+ @dependent_jobs << job
23
+ end
24
+
25
+ def reset_dependent_jobs
26
+ @dependent_jobs = []
27
+ end
28
+
29
+ def dependencies?
30
+ @dependent_jobs.size > 0
31
+ end
32
+
33
+ def allocated?
34
+ @allocation != nil
35
+ end
36
+
37
+ def in_production?
38
+ @production != nil && !@production.finished?
39
+ end
40
+
41
+ def ready?
42
+ @production != nil && @production.finished?
43
+ end
44
+
45
+ def new?
46
+ @allocation == nil && @production == nil && @dependent_jobs.size == 0
47
+ end
48
+
49
+ def allocated_dependencies?
50
+ if dependencies?
51
+ @dependent_jobs.each do |job|
52
+ return false unless job.allocated?
53
+ end
54
+ return true
55
+ else
56
+ return false
57
+ end
58
+ end
59
+
60
+ def to_hash
61
+ core_hash = {
62
+ "id" => self.object_id,
63
+ "resource" => @resource.name,
64
+ }
65
+ core_hash["allocation"] = @allocation.to_hash if @allocation
66
+ core_hash["production"] = @production.to_hash if @production
67
+ core_hash["dependent_jobs"] = @dependent_jobs.map{|j| j.object_id} if @dependent_jobs.size > 0
68
+ core_hash
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,71 @@
1
+ def list_inventory(store)
2
+ puts "---------------------------------------------------------------------------------------"
3
+ puts store.to_s
4
+ puts "---------------------------------------------------------------------------------------"
5
+ puts "Current inventory:"
6
+ puts ""
7
+ store.inventory.each do |item|
8
+ puts "\t#{item.name}: #{store.available_stock(item)} items (allocated: #{store.allocated_stock(item)})"
9
+ end
10
+ end
11
+
12
+ def list_resources(resource_list)
13
+ puts "-------------------------------------------------------------------"
14
+ puts "Registered Resources:"
15
+ puts ""
16
+ resource_list.each do |r|
17
+ puts "\t#{r.to_s}"
18
+ end
19
+ end
20
+
21
+ def list_project(project)
22
+ puts "-------------------------------------------------------------------"
23
+ puts "Project #{project.name}"
24
+ puts ""
25
+ project.resources.each do |r|
26
+ puts "\t#{r.name} - #{project.amount(r)}"
27
+ end
28
+ end
29
+
30
+ def list_factories(factory_list)
31
+ puts "-------------------------------------------------------------------"
32
+ puts "Registered Factories:"
33
+ puts ""
34
+ factory_list.each do |f|
35
+ puts "\t#{f.to_s}"
36
+ end
37
+ end
38
+
39
+ def list_factory(factory)
40
+ puts "-------------------------------------------------------------------"
41
+ puts factory.to_s
42
+ puts "sequential: #{factory.sequential}"
43
+ puts "-------------------------------------------------------------------"
44
+ puts "Available resources:"
45
+ puts ""
46
+ factory.resources.each do |resource|
47
+ puts "\t#{resource.to_s} (Time: #{natural_time(factory.production_time(resource))})"
48
+ end
49
+ puts ""
50
+ puts "Running production"
51
+ factory.productions.each do |production|
52
+ line = "\t#{production.object_id} -> #{production.resource.name}"
53
+ line += " / started at: #{production.start_time}"
54
+ line += " / duration: #{production.duration}"
55
+ line += " / delivery at: #{production.start_time + production.duration}"
56
+ line + " / finished: #{production.finished?}"
57
+ puts line
58
+ end
59
+ end
60
+
61
+ def list_configuration(configuration)
62
+ puts "==================================================================="
63
+ puts "Configuration created at: #{configuration.created_at}"
64
+ puts "==================================================================="
65
+ puts ""
66
+ list_inventory(configuration.store)
67
+ configuration.factories.each do |factory_name|
68
+ puts ""
69
+ outline_factory(configuration.factory(factory_name))
70
+ end
71
+ end
@@ -0,0 +1,13 @@
1
+ require 'smcty/console'
2
+
3
+ module Smcty
4
+ class Planner
5
+ def self.start(path)
6
+ puts "Starting Simcity Production Assistent"
7
+ puts "> load configuration from #{path}"
8
+
9
+ console = Console.new($stdin, path)
10
+ console.prompt!
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ module Smcty
2
+ class Production
3
+ attr_reader :start_time, :duration, :resource
4
+
5
+ def initialize(resource, duration, start_time=nil)
6
+ @start_time = start_time ? Time.at(start_time) : Time.now
7
+ @duration = duration
8
+ @resource = resource
9
+ end
10
+
11
+ def finished?
12
+ end_time <= Time.now
13
+ end
14
+
15
+ def end_time
16
+ @start_time + duration
17
+ end
18
+
19
+ def to_s
20
+ "Production of #{resource.name} since #{@start_time} takes #{natural_time(@duration)} (Finished: #{finished?})"
21
+ end
22
+
23
+ def to_hash
24
+ {
25
+ "start_time" => @start_time.to_i,
26
+ "duration" => @duration,
27
+ "resource" => @resource.name
28
+ }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ module Smcty
2
+ class Project
3
+ attr_reader :name
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ @requirements = {}
8
+ end
9
+
10
+ def resources
11
+ @requirements.keys
12
+ end
13
+
14
+ def amount(resource)
15
+ @requirements[resource]
16
+ end
17
+
18
+ def add_requirement(resource, amount)
19
+ @requirements[resource] = amount
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ module Smcty
2
+ class Resource
3
+ attr_reader :name, :description
4
+
5
+ def initialize(name, description)
6
+ @name = name
7
+ @description = description
8
+ @dependencies = {}
9
+ end
10
+
11
+ def register_dependency(resource, amount)
12
+ @dependencies[resource] = amount
13
+ end
14
+
15
+ def dependent_resources
16
+ @dependencies.keys
17
+ end
18
+
19
+ def dependent_resource_amount(resource)
20
+ @dependencies[resource]
21
+ end
22
+
23
+ def to_s
24
+ "Resource '#{@name}' (#{@description}) with these dependencies: #{dependency_list}"
25
+ end
26
+
27
+ def to_hash(time)
28
+ {
29
+ "name" => @name,
30
+ "description" => @description,
31
+ "time" => time,
32
+ "deps" => @dependencies.keys.map{|r| {"name" => r.name, "amount" => @dependencies[r]}}
33
+ }
34
+ end
35
+
36
+ private
37
+
38
+ def dependency_list
39
+ items = []
40
+ @dependencies.keys.each do |resource|
41
+ items << "#{resource.name}: #{@dependencies[resource]}"
42
+ end
43
+ items.join(", ")
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,198 @@
1
+ require 'smcty/job'
2
+
3
+ module Smcty
4
+ class Scheduling
5
+
6
+ def initialize(configuration)
7
+ @configuration = configuration
8
+ @projects = {}
9
+ end
10
+
11
+ def plan_project(project)
12
+ jobs = []
13
+ @projects[project] = jobs
14
+
15
+ # 1. split the requirements into jobs
16
+ project.resources.each do |r|
17
+ project.amount(r).times do
18
+ jobs << Job.new(r)
19
+ end
20
+ end
21
+
22
+ # 2. work through initial job list and either
23
+ # - allocate available resources
24
+ # - add jobs to produce dependent jobs
25
+ # and iterate until the list does not change any more.
26
+ #
27
+ prev_size = 0
28
+ while (jobs.size != prev_size) do
29
+ prev_size = jobs.size
30
+ jobs.select{|j| j.new? }.each do |job|
31
+ if store.available_stock(job.resource) >= 1
32
+ job.allocate(store.allocate(job.resource, 1))
33
+ else
34
+ job.resource.dependent_resources.each do |dr|
35
+ job.resource.dependent_resource_amount(dr).times do
36
+ djob = Job.new(dr)
37
+ job.add_dependent(djob)
38
+ jobs << djob
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def next
47
+ puts "determine the next action"
48
+ # any project finished?
49
+ action = project_ready
50
+ return action if action
51
+ # any pure request?
52
+ action = something_dependent_to_produce
53
+ return action if action
54
+ # anything to pickup?
55
+ action = something_to_pick
56
+ return action if action
57
+ # anything that can be produced?
58
+ action = something_pure_to_produce
59
+ if action
60
+ return action
61
+ else
62
+ return "wait"
63
+ end
64
+ end
65
+
66
+ def project_ready
67
+ puts " -> is there a project ready for delivery?"
68
+ @projects.keys.each do |project|
69
+ ready = true
70
+ @projects[project].each do |job|
71
+ unless job.allocated?
72
+ ready = false
73
+ break
74
+ end
75
+ end
76
+
77
+ if ready
78
+ finish_project(project)
79
+ # we are done and the project can be finished.
80
+ return "finish #{project.name}"
81
+ end
82
+ end
83
+ nil
84
+ end
85
+
86
+ def finish_project(project)
87
+ # get all the allocated resources
88
+ @projects[project].each do |job|
89
+ job.allocation.get
90
+ end
91
+ # remove the project from scheduling
92
+ @projects.delete(project)
93
+ end
94
+
95
+ def something_pure_to_produce
96
+ puts " -> is there something without dependencies to produce?"
97
+ @projects.keys.each do |project|
98
+ @projects[project].each do |job|
99
+ if job.new?
100
+ factory = @configuration.factory_for(job.resource)
101
+ if factory.free_capacity > 0
102
+ job.produce(factory.produce(job.resource))
103
+ return "produce #{job.resource.name}"
104
+ end
105
+ end
106
+ end
107
+ end
108
+ nil
109
+ end
110
+
111
+ def something_to_pick
112
+ puts " -> is there something to pick?"
113
+ @projects.keys.each do |project|
114
+ @projects[project].each do |job|
115
+ if job.ready? && store.free_capacity > 0
116
+ @configuration.factory_for(job.resource).pick(job.production)
117
+ store.put(job.resource, 1)
118
+ job.allocate(store.allocate(job.resource, 1))
119
+ return "pick #{job.resource.name}"
120
+ end
121
+ end
122
+ end
123
+ nil
124
+ end
125
+
126
+ def something_dependent_to_produce
127
+ puts " -> is there something with dependencies ready for production?"
128
+ @projects.keys.each do |project|
129
+ @projects[project].each do |job|
130
+ if job.allocated_dependencies?
131
+
132
+ # extract the requirements
133
+ requirements = job.dependent_jobs.map{|j| j.allocation }
134
+ # produce the resource
135
+
136
+ factory = @configuration.factory_for(job.resource)
137
+
138
+ if factory.free_capacity > 0
139
+ puts "go for a complex production: #{job.resource}"
140
+ job.produce(factory.produce(job.resource, requirements))
141
+
142
+ # remove the dependent jobs
143
+ job.dependent_jobs.each{|j| @projects[project].delete(j)}
144
+ # reset the job dependency
145
+ job.reset_dependent_jobs
146
+
147
+ return "produce #{job.resource.name}"
148
+ end
149
+ end
150
+ end
151
+ end
152
+ nil
153
+ end
154
+
155
+ # debugging
156
+ def print_job_lists
157
+ @projects.keys.each do |project|
158
+ puts
159
+ puts "Project: #{project.name}"
160
+ puts ""
161
+ @projects[project].each_with_index do |job, idx|
162
+ puts "\t Step-#{idx} - #{job_line(job)}"
163
+ end
164
+ puts
165
+ end
166
+ end
167
+
168
+ def job_line(job)
169
+ "#{job.resource.name}: allocation: #{job.allocation} production: #{job.production} dependent: #{job.dependent_jobs.map{|j| j.resource.name}} new: #{job.new?} ready: #{job.ready?} allocated dependencies: #{job.allocated_dependencies?}"
170
+ end
171
+ # debugging
172
+
173
+ def load_project(project, job_list)
174
+ @projects[project] = job_list
175
+ end
176
+
177
+ def to_hash
178
+ {
179
+ projects: @projects.keys.map{|p| project_hash(p, @projects[p])}
180
+ }
181
+ end
182
+
183
+ def project_hash(project, job_list)
184
+ {
185
+ "name" => project.name,
186
+ "requirements" => project.resources.map{|r| {"resource" => r.name, "amount" => project.amount(r)}},
187
+ "jobs" => job_list.map{|j| j.to_hash}
188
+ }
189
+ end
190
+
191
+ private
192
+
193
+ def store
194
+ @configuration.store
195
+ end
196
+
197
+ end
198
+ end
@@ -0,0 +1,115 @@
1
+ require 'smcty/allocation'
2
+ require 'set'
3
+
4
+ module Smcty
5
+ class Store
6
+ attr_reader :name, :capacity
7
+
8
+ def initialize(name, capacity)
9
+ @name = name
10
+ @capacity = capacity
11
+ @storage = {}
12
+ @allocations = Set.new
13
+ end
14
+
15
+ def free_capacity
16
+ @capacity - total_available_stock - total_allocated_stock
17
+ end
18
+
19
+ def total_available_stock
20
+ result = 0
21
+ @storage.values.each do |value|
22
+ result += value
23
+ end
24
+ result
25
+ end
26
+
27
+ def total_allocated_stock
28
+ result = 0
29
+ @allocations.each do |alloc|
30
+ result += alloc.amount
31
+ end
32
+ result
33
+ end
34
+
35
+ def available_stock(resource)
36
+ @storage[resource] || 0
37
+ end
38
+
39
+ def allocated_stock(resource)
40
+ result = 0
41
+ @allocations.each do |alloc|
42
+ result += alloc.amount if resource == alloc.resource
43
+ end
44
+ result
45
+ end
46
+
47
+ def total_stock(resource)
48
+ available_stock(resource) + allocated_stock(resource)
49
+ end
50
+
51
+ def put(resource, _amount=1)
52
+ # at least one item of the resource
53
+ amount = [1, _amount].max
54
+
55
+ unless free_capacity >= amount
56
+ raise "The store is full - Cannot put #{amount} items of #{resource.name}"
57
+ end
58
+
59
+ in_stock = available_stock(resource)
60
+ @storage[resource] = in_stock + amount
61
+ amount
62
+ end
63
+
64
+ def allocate(resource, _amount)
65
+ # at least one item of the resource
66
+ amount = [1, _amount].max
67
+
68
+ in_stock = available_stock(resource)
69
+ unless in_stock >= amount
70
+ raise "#{in_stock} is not enough of #{resource.name} to allocate #{amount} items"
71
+ end
72
+
73
+ @storage[resource] = in_stock - amount
74
+ allocation = Allocation.new(self, resource, amount)
75
+ @allocations.add(allocation)
76
+ allocation
77
+ end
78
+
79
+ def get(allocation)
80
+ @allocations.delete(allocation)
81
+ end
82
+
83
+ def free(allocation)
84
+ put(allocation.resource, allocation.amount)
85
+ @allocations.delete(allocation)
86
+ end
87
+
88
+ def valid?(allocation)
89
+ @allocations.include?(allocation)
90
+ end
91
+
92
+ def inventory
93
+ @storage.keys
94
+ end
95
+
96
+ def to_s
97
+ "Store #{@name} has a total capacity of #{@capacity} whereas the free capacity is #{free_capacity}"
98
+ end
99
+
100
+ def to_hash
101
+ {
102
+ "name" => @name,
103
+ "capacity" => @capacity,
104
+ "stock" => @storage.keys.map{|k| {"name" => k.name, "amount" => total_stock(k)}}
105
+ }
106
+ end
107
+
108
+ private
109
+
110
+ def enforce(lower, amount, upper)
111
+ [[lower, amount].max, upper].min
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,3 @@
1
+ module Smcty
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smcty
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Markus Kuhnt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-12-25 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Plan your production based on scheduled plans to optimize throughput.
14
+ email:
15
+ - markus.kuhnt@gmail.com
16
+ executables:
17
+ - smcty
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - bin/smcty
22
+ - lib/smcty.rb
23
+ - lib/smcty/allocation.rb
24
+ - lib/smcty/configuration.rb
25
+ - lib/smcty/configurator.rb
26
+ - lib/smcty/console.rb
27
+ - lib/smcty/factory.rb
28
+ - lib/smcty/helpers.rb
29
+ - lib/smcty/job.rb
30
+ - lib/smcty/output.rb
31
+ - lib/smcty/planner.rb
32
+ - lib/smcty/production.rb
33
+ - lib/smcty/project.rb
34
+ - lib/smcty/resource.rb
35
+ - lib/smcty/scheduling.rb
36
+ - lib/smcty/store.rb
37
+ - lib/smcty/version.rb
38
+ homepage:
39
+ licenses:
40
+ - MIT
41
+ metadata: {}
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 1.3.6
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 2.4.5.1
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: A production planning software for simcity(tm)
62
+ test_files: []