Created
May 25, 2009 17:53
-
-
Save patroza/117639 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
gem 'log4r' | |
gem 'win32-pipe' | |
gem 'windows-pr' | |
require 'log4r' | |
require 'win32/pipe' | |
#require 'win32/mutex' # Uhh, how to use? | |
require 'mutex_m' | |
require 'types' | |
require 'sqf' | |
require 'database' | |
DEBUG_LOG = false | |
# Extend the Pipe class | |
module Win32 | |
class Pipe | |
EMPTY = "\x00\x00\x00\x00" # [0].pack('L') # Works but seems slow! | |
# "\x00\x00\x00\x00" | |
#def PeekNamedPipe(name, buf, buf_size, bytes_read, bytes_avail, bytes_left) | |
def peek(read = "\x00\x00\x00\x00", avail = "\x00\x00\x00\x00", remaining = "\x00\x00\x00\x00") | |
PeekNamedPipe(@pipe, @buffer, PIPE_BUFFER_SIZE, read, avail, remaining) | |
avail != EMPTY | |
end | |
end | |
end | |
module Win32 | |
ERROR_PIPE_BUSY = -1 | |
end | |
module RarmaLink | |
include Win32 | |
# Enable any object to manage its own synchronisation. | |
# obj = Object.new | |
# obj.extend Mutex_m | |
class ArraySync < Array | |
#include Mutex_m # initialize doesnt get properly updated with this include, thus we use extend | |
public | |
def try_synchronize | |
if mu_try_lock | |
begin | |
yield self | |
#mu_unlock | |
rescue => e | |
puts "Rescue!!" | |
mu_unlock | |
raise e | |
ensure | |
mu_unlock | |
end | |
end | |
end | |
public | |
def do_synchronize | |
begin | |
# TODO: Evaluate if this is cool :) | |
mu_lock | |
yield self | |
#mu unlock | |
rescue => e | |
puts "Rescue!!" | |
mu_unlock | |
raise e | |
ensure | |
mu_unlock | |
end | |
end | |
end | |
COMPONENT = 'RarmaLink' | |
PIPE_BASE = "\\\\.\\pipe\\scriptlink_" | |
PIPE_BASE2 = 'scriptlink_' | |
@@log = Log4r::Logger.new(COMPONENT) | |
format1 = Log4r::PatternFormatter.new(:pattern => "[%l] %d: %m", :date_pattern => '%H:%M:%S') | |
format2 = Log4r::PatternFormatter.new(:pattern => "[%l] %c %d: %m", :date_pattern => '%H:%M:%S') | |
# Create Outputters | |
if DEBUG_LOG | |
o_file = Log4r::FileOutputter.new 'rarma-file', | |
'level' => 0, # All | |
:filename => "#{COMPONENT}.log", | |
'formatter' => format2 | |
#:maxsize => 1024 | |
@@log.outputters << o_file | |
end | |
o_out = Log4r::StdoutOutputter.new 'rarma-stdout', | |
'level' => 2, # no DEBUG | |
'formatter' => format1 | |
o_err = Log4r::StderrOutputter.new 'rarma-stderr', | |
'level' => 4, # Error and Up | |
'formatter' => format1 | |
@@log.outputters << o_out << o_err | |
def log | |
@@log | |
end | |
class ScriptLink | |
# pipe_name:: Name of the pipe. | |
# message_handler:: Callback for any received messages. | |
def initialize(pipe_name = PIPE_NAME, &message_handler) | |
@pipe_name, @message_handler = pipe_name, message_handler | |
@closed = false | |
@data = ArraySync.new | |
@data.extend Mutex_m | |
@data_db = ArraySync.new | |
@data_db.extend Mutex_m | |
# @data_write = ArraySync.new | |
# @data_write.extend Mutex_m | |
pipe_mode = Pipe::NOWAIT | |
open_mode = Pipe::PIPE_ACCESS_DUPLEX | File::RDWR # | Pipe::OVERLAPPED | |
begin | |
@pipe = Pipe::Client.new("#{PIPE_BASE2}#{pipe_name}", pipe_mode, open_mode)#, nil, open_mode) | |
rescue Pipe::Error => exception | |
raise exception, "#{exception.message} - Could not open named pipe '#{PIPE_BASE}#{pipe_name}'" | |
end | |
puts ["Closed?", closed?] | |
write "Connected" | |
read_thread = true | |
data_thread = true | |
db_thread = true # Fast operation when db is not used | |
@read_thread = Thread.new do | |
buff = [] | |
#buffer = "" | |
#sleep 5 | |
until closed? | |
if @pipe.peek(nil) # FIXME: EHRR UPDATE: SEEMS TO BE FINE SOMEHOW NOW! - Seems to be a slow function, slowing down this thread, meaning also slowed down arma operations! | |
#puts "Peeked and found!" | |
buff += @pipe.read | |
# buffer = @pipe.buffer | |
end | |
#if buffer.size > 0 | |
#buff << buffer.strip | |
#sleep 0.1 # This will make the data come in complete somehow :S | |
#end | |
@data.try_synchronize do |sync_data| | |
#puts "read thread locking @data; #{buff}" | |
buff.each { |b| sync_data << b } | |
buff.clear | |
end if buff.size > 0 | |
sleep 0.001 # only when peeked and nothing found - otherwise slowsdown game also? | |
end | |
end if read_thread | |
=begin | |
@write_thread = Thread.new do | |
loop do | |
exec = [] | |
loop do | |
@data_write.do_synchronize do |sync_data_write| | |
exec += sync_data_write | |
sync_data_write.clear | |
end | |
#@pipe.try_synchronize do | |
exec.each { |data| write_real(data) } | |
exec.clear | |
#end if exec.size > 0 | |
end | |
end | |
end | |
=end | |
@data_thread = Thread.new do | |
buff = [] | |
exec = [] | |
keep = "" | |
partial = false | |
loop do | |
#begin | |
@data.do_synchronize do |sync_data| | |
#puts "data thread locking @data #{sync_data}" | |
exec += sync_data | |
sync_data.clear | |
end | |
#rescue | |
# puts "Sync gives error!" | |
#end | |
exec.each do |data| | |
if data[/.*x00.*/] | |
# puts "Data: #{data}" | |
if partial | |
puts "Keep: #{keep}" | |
puts "msgs: #{data}" | |
data = keep + data | |
keep = "" | |
partial = false | |
end | |
#data.strip! | |
ar = data.split("x00") | |
unless data[/.*x00\Z/] #|| !(data =~ /.*\x00.*/) | |
puts "Partial Found, ArraySize: #{ar.size}" | |
partial = true | |
keep += ar.last | |
ar = ar - [ar.last] | |
end | |
ar.each do |message| | |
begin | |
#puts "Message: #{message}" | |
#@message_handler.call(Sqf.parse(data)) | |
received = Sqf.parse(message) | |
puts "Received! #{received.inspect}" | |
rescue | |
puts "Bad Data found: #{message}" | |
end | |
if received.class == Array | |
case received[0] | |
when /RARMA_STATS.*/ #"RARMA_STATS" | |
buff << received #[1] | |
when "RARMA_DATE" | |
puts "Received Date Request!" | |
# date = Time.new | |
# write Code.new("setDate [#{date.year},#{date.month},#{date.day},#{date.hour},#{date.min}]") # Crashes Ruby now? - Seperate Write ? | |
end | |
end | |
end | |
else | |
puts "Message found without escape, Partial!" | |
partial = true | |
keep += data | |
end | |
end | |
exec.clear | |
# TODO: Is it possible that this "@data_db." slows down when the db thread is processing heavily? | |
# - Look into "Condition Variables ? | |
@data_db.try_synchronize do |sync_data_db| | |
#puts "data thread locking @data_db" | |
buff.each { |b| sync_data_db << b } if db_thread | |
#buff.each { |b| insert_data(:stats, b) } # Seems to go somewhat better when directly done in this thread | |
buff.clear | |
end if buff.size > 0 | |
sleep 0.01 | |
end | |
end if data_thread | |
# TODO: Might be a bit lame implementation | |
@missioninstance = nil | |
@frame = nil | |
@group = nil | |
@db_thread = Thread.new do | |
exec = [] | |
loop do | |
@data_db.do_synchronize do |sync_data_db| | |
#puts "db thread locking @data_db" | |
exec += sync_data_db | |
sync_data_db.clear | |
end | |
exec.each do |data| | |
#puts "DB Thread: #{data}" | |
case data[0] | |
when "RARMA_STATS_M" | |
@missioninstance = rarma_stats_m(data[1]) | |
when "RARMA_STATS_F" | |
@frame = rarma_stats_f(data[1], @missioninstance) | |
when "RARMA_STATS_G" | |
@group = rarma_stats_g(data[1], @frame) | |
when "RARMA_STATS_U" | |
unit = rarma_stats_u(data[1], @group) | |
when "RARMA_STATS_V" | |
vehicle = rarma_stats_v(data[1], @frame) | |
end | |
#insert_data(:stats, row) | |
#begin | |
# #puts "DBData: #{row}" | |
# insert_data(:stats, row) | |
#rescue | |
# puts "DB Problem: #{row}" | |
#end | |
end | |
# puts "#{exec.size}" | |
exec.clear | |
sleep 0.01 | |
# sleep 1 # seems to sometimes help with db processing... :-| | |
end | |
end if db_thread | |
@stats_thread = Thread.new do | |
loop do | |
puts "HeartBeat, Read: #{@read_thread.alive? if read_thread} Data: #{@data_thread.alive? if data_thread} DB: #{@db_thread.alive? if db_thread}" | |
sleep 1 | |
end | |
end | |
end | |
def createunit(group, type, position, markers, placement, special) | |
# http://community.bistudio.com/wiki/createUnit_arrays | |
Code.new("#{group} createUnit [\"#{type}\", #{position}, #{markers}, #{placement}, \"#{special}\"]") | |
end | |
# World, Mission and MissionInstance data | |
def rarma_stats_m(data) | |
puts "rarma_stats_m" | |
world_name = data[0] | |
mission_name = data[1] | |
# Find or create World | |
world = World.find(:first, :conditions => { :worldname => world_name }) | |
unless world | |
puts "Creating new world" | |
world = World.new | |
#world.name = # calculate from some saved data, or actually get it from the game? | |
world.worldname = world_name | |
world.save | |
end | |
# Find or create Mission | |
mission = Mission.find(:first, :conditions => { :world_id => world, :name => mission_name}) | |
unless mission | |
puts "Creating new mission" | |
mission = Mission.new | |
mission.name = mission_name | |
mission.world = world | |
mission.save | |
end | |
# Create Mission Instance | |
puts "Creating new missioninstance" | |
missioninstance = Missioninstance.new | |
missioninstance.name = 'Test' | |
missioninstance.mission = mission | |
missioninstance.save | |
missioninstance | |
end | |
# Frame | |
# TODO: Evaluate if we actually need to send the frame number from the game :P | |
def rarma_stats_f(data, missioninstance) | |
puts "rarma_stats_f" | |
h = Hash.new | |
# FIXME: NASTY! | |
date = data[2] | |
data[2] = '#{date[0]}-#{date[1]}-#{date[2]} #{date[3]}:#{date[4]}:#{date[5]}' | |
data.each_with_index { |d, idx| h[FIELDS_F[idx].keys[0]] = d } | |
# TODO: Evaluate 'create' and then alter settings, and then 'save'; it will first create, then update record (= 2 queries!) | |
# Probably can just do: h[:missioninstance] = missioninstance (or h[:missioninstance_id] = missioninstance) | |
frame = Frame.create(h) | |
frame.missioninstance = missioninstance | |
frame.save | |
frame | |
end | |
# Group | |
def rarma_stats_g(data, frame) | |
puts "rarma_stats_g" | |
h = Hash.new | |
data.each_with_index { |d, idx| h[FIELDS_G[idx].keys[0]] = d } | |
group = Group.create(h) | |
group.frame = frame | |
group.save | |
group | |
end | |
# Units | |
def rarma_stats_u(data, group) | |
puts "rarma_stats_u" | |
h = Hash.new | |
data.each_with_index { |d, idx| h[FIELDS_U[idx].keys[0]] = d } | |
unit = Unit.create(h) | |
unit.group = group | |
unit.save | |
unit | |
end | |
# Vehicles | |
def rarma_stats_v(data, frame) | |
puts "rarma_stats_v" | |
h = Hash.new | |
data.each_with_index { |d, idx| h[FIELDS_V[idx].keys[0]] = d } | |
vehicle = Vehicle.create(h) | |
vehicle.frame = frame | |
vehicle.save | |
vehicle | |
end | |
def stats_loop | |
# Test injection of Loop | |
# TODO: Should wrap all things into Ruby, then use those methods instead | |
str = <<END_OF_STR | |
RARMA_STATS_LOOP = true; | |
_pid = [] spawn | |
{ | |
private ["_data", "_grp", "_frame"]; | |
_frame = 0; | |
["rarma", ["RARMA_STATS_M", [#{(COMMANDS_W+COMMANDS_M).join(',')}]]] call rarma_send; | |
while { true } do | |
{ | |
["rarma", ["RARMA_STATS_F", [#{COMMANDS_F.join(',')}]]] call rarma_send; | |
_frame = _frame + 1; | |
{ | |
_grp = _x; | |
["rarma", ["RARMA_STATS_G", [#{COMMANDS_G.join(',')}]]] call rarma_send; | |
{ | |
_data = ["RARMA_STATS_U", [#{COMMANDS_U.join(',')}]]; | |
["rarma", _data] call rarma_send; | |
} forEach (units _x); | |
} forEach AllGroups; | |
sleep 10; | |
if !(RARMA_STATS_LOOP) exitWith { RARMA_STATS_LOOP = nil }; | |
}; | |
}; | |
_instances = _instances + [_pid]; | |
END_OF_STR | |
# sleep seems to help somewhat! | |
Code.new(str) | |
end | |
#method_alias :open, :new | |
def write_real(data) | |
# ArmaLib pipe desires zero-terminated strings. | |
puts "['Sending', #{data}]" | |
1.times { @pipe.write(data) } | |
puts "['Sent', #{data}]" | |
#sleep 0.1 | |
end | |
# Send Ruby data to ArmA. | |
# data:: Data to send. | |
public | |
def write(data) | |
data = data.to_sqf + "\0" | |
#@data_write.try_synchronize do |sync_data_write| | |
# sync_data_write << data | |
#end | |
write_real(data) | |
end | |
def fetch(data) | |
data = "[\"#{@pipe_name}\", #{data}] call rarma_request;" | |
write(Code.new(data)) | |
end | |
# Has the link closed? | |
public | |
def closed? | |
@closed | |
end | |
# Closes the link. | |
public | |
def close | |
@pipe.disconnect | |
@pipe.close | |
@closed = true | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment