aboutsummaryrefslogtreecommitdiff
path: root/bin/modules/common.py
blob: 314c3aab26116f0fc7ddee0de666ce8d731d8194 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
""" SALIS: Viewer/controller for the SALIS simulator.

File: common.py
Author: Paul Oliver
Email: paul.t.oliver.design@gmail.com

Network communications module for Salis simulator. This module allows for IPC
between individual Salis organisms and different simulations via UDP sockets.
"""

import json
import os
import socket

from ctypes import c_int, c_uint8, CFUNCTYPE


class Common:
	SENDER_TYPE = CFUNCTYPE(None, c_uint8)
	RECEIVER_TYPE = CFUNCTYPE(c_uint8)

	def __init__(self, sim, max_buffer_size=4096):
		""" Initialize module with a default buffer size of 4 KiB.
		"""
		self.__sim = sim
		self.__settings_path = self.__get_settings_path()
		self.max_buffer_size = max_buffer_size
		self.in_buffer = bytearray()
		self.out_buffer = bytearray()
		self.sources = []
		self.targets = []

		# Use a global client socket for all output operations.
		self.__client = self.__get_socket()

	def define_functors(self):
		""" Define the C callbacks which we'll pass to the Salis simulator.
		These simply push and pop instructions from the input and output
		buffers whenever organisms call the SEND and RCVE instructions.
		"""
		def sender(inst):
			if len(self.out_buffer) < self.max_buffer_size:
				self.out_buffer.append(inst)

		def receiver():
			if len(self.in_buffer):
				res = self.in_buffer[0]
				self.in_buffer = self.in_buffer[1:]
				return c_uint8(res)
			else:
				return c_uint8(0)

		self.__sender = self.SENDER_TYPE(sender)
		self.__receiver = self.RECEIVER_TYPE(receiver)
		self.__sim.lib.sal_comm_set_sender(self.__sender)
		self.__sim.lib.sal_comm_set_receiver(self.__receiver)

	def add_source(self, address, port):
		""" Create new input socket.
		"""
		sock = self.__get_server(address, port)
		self.sources.append(sock)

	def add_target(self, address, port):
		""" Create new output address/port tuple. We use global output socket
		('self.__client') for output operations.
		"""
		self.targets.append((address, port))

	def remove_source(self, address, port):
		""" Remove an input socket.
		"""
		source = (address, port)
		self.sources = [s for s in self.sources if s.getsockname() != source]

	def remove_target(self, address, port):
		""" Remove an output address/port pair.
		"""
		target = (address, port)
		self.targets = [t for t in self.targets if t != target]

	def link_to_self(self, port):
		""" Create input and output links to 'localhost'.
		"""
		self.add_source(socket.gethostbyname(socket.gethostname()), port)
		self.add_target(socket.gethostbyname(socket.gethostname()), port)

	def cycle(self):
		""" We push all data on the output buffer to all targets and clear it.
		We withdraw incoming data from all source sockets and append it to the
		input buffer.
		"""
		if len(self.out_buffer) and self.targets:
			for target in self.targets:
				self.__client.sendto(self.out_buffer, target)

			# Clear output buffer.
			self.out_buffer = bytearray()

		# Receive data and store on input buffer.
		if len(self.in_buffer) < self.max_buffer_size:
			for source in self.sources:
				try:
					self.in_buffer += source.recv(
						self.max_buffer_size - len(self.in_buffer)
					)
				except socket.error:
					pass

	def load_network_config(self, filename):
		""" Load network configuration from a JSON file.
		"""
		with open(os.path.join(self.__settings_path, filename), "r") as f:
			in_dict = json.load(f)

		self.max_buffer_size = in_dict["max_buffer_size"]

		for source in in_dict["sources"]:
			self.add_source(*source)

		for target in in_dict["targets"]:
			self.add_target(*target)

		for inst in in_dict["in_buffer"]:
			self.in_buffer.append(self.__sim.handler.inst_dict[inst])

		for inst in in_dict["out_buffer"]:
			self.out_buffer.append(self.__sim.handler.inst_dict[inst])

	def save_network_config(self, filename):
		""" Save network configuration to a JSON file.
		"""
		out_dict = {
			"max_buffer_size": self.max_buffer_size,
			"in_buffer": "",
			"out_buffer": "",
			"sources": [s.getsockname() for s in self.sources],
			"targets": self.targets,
		}

		for byte in self.in_buffer:
			out_dict["in_buffer"] += self.__sim.printer.inst_list[byte][1]

		for byte in self.out_buffer:
			out_dict["out_buffer"] += self.__sim.printer.inst_list[byte][1]

		with open(os.path.join(self.__settings_path, filename), "w") as f:
			json.dump(out_dict, f, indent="\t")


	###############################
	# Private methods
	###############################

	def __get_settings_path(self):
		""" Get path to network settings directory.
		"""
		self_path = os.path.dirname(__file__)
		return os.path.join(self_path, "../network")

	def __get_socket(self):
		""" Generate a non-blocking UDP socket.
		"""
		sock = socket.socket(
			socket.AF_INET, socket.SOCK_DGRAM | socket.SOCK_NONBLOCK
		)
		return sock

	def __get_server(self, address, port):
		""" Generate a socket and bind to an address/port pair.
		"""
		serv_socket = self.__get_socket()
		serv_socket.bind((address, port))
		return serv_socket