Skip to content

Instantly share code, notes, and snippets.

@patroza
Created May 25, 2009 17:53
Show Gist options
  • Save patroza/117639 to your computer and use it in GitHub Desktop.
Save patroza/117639 to your computer and use it in GitHub Desktop.
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