Skip to content

Commit

Permalink
network messaging
Browse files Browse the repository at this point in the history
  • Loading branch information
Trilarion committed Oct 9, 2014
1 parent d531574 commit 3514542
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 39 deletions.
4 changes: 2 additions & 2 deletions source/client/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ class Player(QtCore.QObject):

next = QtCore.Signal(str)

def __init__(self):
def __init__(self, parent=None):
"""
Setups the sound system.
Ticks are not implemented because we don't need them but in case we do it is fairly simple.
TODO what if there are no sound capabilities, is this sure at this point that we have them?
"""
super().__init__()
super().__init__(parent=parent)

# set up audio output and media object and connect both
self.audio_output = Phonon.AudioOutput(Phonon.MusicCategory, self)
Expand Down
74 changes: 62 additions & 12 deletions source/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# TODO automatic placement of help dialog depending on if another dialog is open
# TODO help dialog has close button in focus initially (why?) remove this

import json, os
import json, os, zlib, codecs

from PySide import QtCore, QtGui

Expand Down Expand Up @@ -151,8 +151,43 @@ def scenario_read_as_preview(file_name):
scenario = Scenario()
scenario.load(file_name)
preview = {}
preview['message.id'] = 'preview'
preview['message.file'] = file_name

# scenario copy keys
scenario_copy_keys = [TITLE, MAP_COLUMNS, MAP_ROWS]
for key in scenario_copy_keys:
preview[key] = scenario[key]

# copy a bit of nations
nations = {}
nation_copy_keys = ['color', 'name']
for nation in scenario.all_nations():
nations[nation] = {}
for key in nation_copy_keys:
nations[key] = scenario.get_nation_property(nation, key)
preview['nations'] = nations

# assemble map
columns = scenario[MAP_COLUMNS]
rows = scenario[MAP_ROWS]
map = [0] * (columns * rows)
for nation in scenario.all_nations():
provinces = scenario.get_provinces_of_nation(nation)
for province in provinces:
tiles = scenario.get_province_property(province, 'tiles')
for column, row in tiles:
map[row * columns + column] = nation
preview['map'] = map

# convert to json
message = json.dumps(preview, separators=(',',':'))

# zip it
compressed = zlib.compress(message.encode())

return compressed

return preview

class SinglePlayerScenarioSelection(QtGui.QWidget):

Expand Down Expand Up @@ -180,8 +215,8 @@ def __init__(self):
layout.addWidget(map, 0, 1, 2)
scenario_alternative_button = QtGui.QPushButton('Select another')
layout.addWidget(scenario_alternative_button, 1, 0)
info = QtGui.QWidget()
layout.addWidget(info, 2, 0, 1, 2)
self.info = QtGui.QLabel()
layout.addWidget(self.info, 2, 0, 1, 2)
layout.setRowStretch(2, 1) # infobox gets all the height
layout.setColumnStretch(1, 1) # map gets all the width

Expand All @@ -191,7 +226,12 @@ def list_selection_changed(self):
self.new_selected_scenario(file_name)

def new_selected_scenario(self, file_name):
preview = scenario_read_as_preview(file_name)
compressed = scenario_read_as_preview(file_name)
message = zlib.decompress(compressed).decode()
preview = json.loads(message)
text = 'Title: {}'.format(preview[TITLE])
text += '<br>Number nations: {}'.format(len(preview['nations']))
self.info.setText(text)


class GameLobbyWidget(QtGui.QWidget):
Expand Down Expand Up @@ -261,7 +301,6 @@ def __init__(self):

toolbar = QtGui.QToolBar()
toolbar.setIconSize(QtCore.QSize(32, 32))

action_group = QtGui.QActionGroup(toolbar)

action_initial = g.create_action(t.load_ui_icon('icon.preferences.general.png'), 'Show general preferences', action_group, self.show_tab_general, True)
Expand Down Expand Up @@ -444,12 +483,6 @@ def __init__(self):
# main window
self.main_window = MainWindow()

# add server monitor
action = QtGui.QAction(self.main_window)
action.setShortcut(QtGui.QKeySequence('F1'))
action.triggered.connect(self.show_server_monitor)
self.main_window.addAction(action)

# help browser
self.help_browser_widget = BrowserWidget(QtCore.QUrl(c.Manual_Index), t.load_ui_icon)
self.help_dialog = cg.GameDialog(self.main_window, self.help_browser_widget, title='Help')
Expand All @@ -458,6 +491,18 @@ def __init__(self):
self.help_dialog.move(self.main_window.x() + self.main_window.width() - 800,
self.main_window.y() + self.main_window.height() - 600)

# add help browser keyboard shortcut
action = QtGui.QAction(self.main_window)
action.setShortcut(QtGui.QKeySequence('F1'))
action.triggered.connect(self.show_help_browser)
self.main_window.addAction(action)

# add server monitor keyboard shortcut
action = QtGui.QAction(self.main_window)
action.setShortcut(QtGui.QKeySequence('F2'))
action.triggered.connect(self.show_server_monitor)
self.main_window.addAction(action)

# for the notifications
self.pending_notifications = []
self.notification_position_constraint = g.RelativeLayoutConstraint().centerH().south(20)
Expand All @@ -471,6 +516,10 @@ def __init__(self):
if not t.options[c.OM_BG_MUTE]:
self.player.start()

# after the player starts, the main window is not active anymore
# set it active again or it doesn't get keyboard focus
self.main_window.activateWindow()

def audio_notification(self, title):
"""
Special kind of notification from the audio system.
Expand Down Expand Up @@ -512,6 +561,7 @@ def show_help_browser(self, url=None):
def show_server_monitor(self):
monitor_widget = ServerMonitorWidget()
dialog = cg.GameDialog(self.main_window, monitor_widget, delete_on_close=True, title='Server Monitor')
dialog.setFixedSize(QtCore.QSize(800, 600))
dialog.show()

def switch_to_start_screen(self):
Expand Down
19 changes: 9 additions & 10 deletions source/client/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from PySide import QtCore, QtNetwork

from lib.network import *

class Client(QtCore.QObject):
"""
Expand All @@ -35,13 +36,13 @@ def __init__(self):
self.socket = QtNetwork.QTcpSocket(self)
# some connections
self.socket.readyRead.connect(self.receive)
self.error.connect(self.error)
self.socket.error.connect(self.error)

def login(self, address):
def login(self, host, port):
"""
Given an address (list of two parts: hostname (str), port (int)) tries to connect the socket.
"""
self.socket.connectToHost(address[0], address[1])
self.socket.connectToHost(host, port)

def error(self):
"""
Expand All @@ -55,16 +56,14 @@ def receive(self):
TODO will the whole message arrive in one piece?
"""
reader = QtCore.QDataStream(self.socket)
message = reader.readString()
self.received.emit(message)
value = read_from_socket_uncompress_and_deserialize(self.socket)
print('client received {}'.format(json.dumps(value)))
self.received.emit(value)

def send(self, message):
def send(self, value):
"""
Sends a message (a astring) over the socket.
TODO check if connected before, error if not
"""
writer = QtCore.QDataStream(self.socket)
writer.setVersion(QtCore.QDataStream.Qt_4_8)
writer.writeString(message)
serialize_compress_and_write_to_socket(self.socket, value)
7 changes: 4 additions & 3 deletions source/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"""

import os

from PySide import QtNetwork

def extend(path, *parts):
"""
Expand Down Expand Up @@ -61,8 +61,9 @@ def extend(path, *parts):

# other specific constants

# port number for network communication
Network_Port = 42932
# network communication
LOCALHOST = QtNetwork.QHostAddress.LocalHost
NETWORK_PORT = 42932

# minimal screen resolution
Screen_Min_Size = (1024, 768)
Expand Down
1 change: 1 addition & 0 deletions source/server/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ def __init__(self, client):
self.scenario.everything_changed.connect(self.scenario_change)

self.toolbar = QtGui.QToolBar()
self.toolbar.setIconSize(QtCore.QSize(32, 32))
self.toolbar.addAction(g.create_action(t.load_ui_icon('icon.scenario.new.png'), 'Create new scenario', self, self.show_new_scenario_dialog))
self.toolbar.addAction(g.create_action(t.load_ui_icon('icon.scenario.load.png'), 'Load scenario', self, self.load_scenario_dialog))
self.toolbar.addAction(g.create_action(t.load_ui_icon('icon.scenario.save.png'), 'Save scenario', self, self.save_scenario_dialog))
Expand Down
19 changes: 18 additions & 1 deletion source/server/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,25 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>

from PySide import QtCore, QtGui
from server.network import local_server

class ServerMonitorWidget(QtGui.QWidget):

def __init__(self):
super().__init__()
super().__init__()

layout = QtGui.QGridLayout(self)

self.status_label = QtGui.QLabel()
layout.addWidget(self.status_label, 0, 0)

# set timer for update
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.update_monitor)
self.timer.setInterval(60000) # every second
self.timer.start()
self.update_monitor()

def update_monitor(self):
text = '{} connections'.format(len(local_server.connections))
self.status_label.setText(text)
21 changes: 12 additions & 9 deletions source/server/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from PySide import QtCore, QtNetwork

from lib.network import *

class Server(QtCore.QObject):
"""
Expand All @@ -47,11 +48,11 @@ def create_id(self):
if id not in self.connections:
return id

def start(self, address):
def start(self, host, port):
"""
Given an address (hostname, port) tries to start listening.
"""
if not self.server.listen(address[0], address[1]):
if not self.server.listen(host, port):
raise RuntimeError('Network error: cannot listen')

def stop(self):
Expand All @@ -66,10 +67,11 @@ def new_connection(self):
Zero or more new connections might be available, give them an id and wire them.
"""
while self.server.hasPendingConnections():
socket = self.server.nextPendingConnection()
socket = self.server.nextPendingConnection() # returns a QTcpSocket
# get id
id = self.create_id()
self.connections[id] = socket
print('new connection id {}, address {}, port {}'.format(id, socket.peerAddress().toString(), socket.peerPort()))
# connect
socket.disconnected.connect(partial(self.disconnected, id))
socket.readyRead.connect(partial(self.receive, id))
Expand All @@ -92,14 +94,15 @@ def receive(self, id):
A certain connection (identified by its id) wants to send us something.
"""
socket = self.connections[id]
reader = QtCore.QDataStream(socket)
message = reader.readString()
value = read_from_socket_uncompress_and_deserialize(socket)
print('connection id {} received {}'.format(id, json.dumps(value)))

def send(self, id, message):
def send(self, id, value):
"""
We send a message to a certain connection (identified by its id).
"""
socket = self.connections[id]
writer = QtCore.QDataStream(socket)
writer.setVersion(QtCore.QDataStream.Qt_4_8)
writer.writeString(message)
serialize_compress_and_write_to_socket(socket, value)

# create a local server
local_server = Server()
8 changes: 6 additions & 2 deletions source/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,13 @@
t.options[c.OG_MW_FULLSCREEN] = False

# now we can safely assume that the environment is good to us
# and we simply start the client
from client import client

# start local server
from server.network import local_server
local_server.start(c.LOCALHOST, c.NETWORK_PORT)

# start client
from client import client
client.start()

# save options
Expand Down
23 changes: 23 additions & 0 deletions test/server/network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from PySide import QtCore, QtNetwork
import constants as c
import server.network as snet
import client.network as cnet

def setup():
snet.local_server.start(c.LOCALHOST, c.NETWORK_PORT)
client.login(c.LOCALHOST, c.NETWORK_PORT)

def send():
d = {'car':4,89:'dzhuifhe'}
client.send(d)
for id in snet.local_server.connections:
snet.local_server.send(id, d)

app = QtCore.QCoreApplication([])

client = cnet.Client()

QtCore.QTimer.singleShot(0, setup)
QtCore.QTimer.singleShot(100, send)
QtCore.QTimer.singleShot(3000, app.quit)
app.exec_()

0 comments on commit 3514542

Please sign in to comment.