-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
1,447 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
name: lattice_gym | ||
channels: | ||
- defaults | ||
dependencies: | ||
- _libgcc_mutex=0.1=main | ||
- _openmp_mutex=5.1=1_gnu | ||
- bzip2=1.0.8=h5eee18b_6 | ||
- ca-certificates=2024.9.24=h06a4308_0 | ||
- expat=2.6.3=h6a678d5_0 | ||
- ld_impl_linux-64=2.40=h12ee557_0 | ||
- libffi=3.4.4=h6a678d5_1 | ||
- libgcc-ng=11.2.0=h1234567_1 | ||
- libgomp=11.2.0=h1234567_1 | ||
- libstdcxx-ng=11.2.0=h1234567_1 | ||
- libuuid=1.41.5=h5eee18b_0 | ||
- ncurses=6.4=h6a678d5_0 | ||
- openssl=3.0.15=h5eee18b_0 | ||
- pip=24.2=py312h06a4308_0 | ||
- python=3.12.5=h5148396_1 | ||
- readline=8.2=h5eee18b_0 | ||
- setuptools=75.1.0=py312h06a4308_0 | ||
- sqlite=3.45.3=h5eee18b_0 | ||
- tk=8.6.14=h39e8969_0 | ||
- tzdata=2024a=h04d1e81_0 | ||
- wheel=0.44.0=py312h06a4308_0 | ||
- xz=5.4.6=h5eee18b_1 | ||
- zlib=1.2.13=h5eee18b_1 | ||
- pip: | ||
- pyqt6==6.7.1 | ||
- pyqt6-qt6==6.7.3 | ||
- pyqt6-sip==13.8.0 |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import sys | ||
from typing import Tuple, List, Set | ||
from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QLineEdit | ||
import random | ||
import socket | ||
import threading | ||
import time | ||
from functools import partial | ||
|
||
|
||
class MyApp(QWidget): | ||
def __init__(self): | ||
super().__init__() | ||
self.setWindowTitle("Lattice Gym") | ||
self.resize(300, 300) # width, height | ||
layout = QVBoxLayout() | ||
self.setLayout(layout) | ||
|
||
# socket parameters | ||
self.SERVER_PORT: int = 1234 | ||
self.CLIENT_PORT: int = random.randint(5000, 9000) | ||
self.SERVER_NAME: str = "localhost" | ||
self.SERVER_ADDRESS: Tuple[str, int] = (self.SERVER_NAME, self.SERVER_PORT) | ||
self.CLIENT_ADDRESS: Tuple[str, int] = (self.SERVER_NAME, self.CLIENT_PORT) | ||
self.DATA_FORMAT: str = "utf-8" | ||
self.DISCONNECTION_INFO: str = "!DISCONNECT" | ||
|
||
self.client: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
self.client.bind(self.CLIENT_ADDRESS) | ||
|
||
# widgets | ||
self.state_label = QLabel() | ||
self.state_label.setText("Current State: ") | ||
self.info_label = QLabel() | ||
self.info_label.setText("Information: ") | ||
|
||
self.add_text = QLineEdit() | ||
self.add_text.setPlaceholderText("Enter the element to add") | ||
self.add_button = QPushButton() | ||
self.add_button.setText("Add") | ||
self.remove_text = QLineEdit() | ||
self.remove_text.setPlaceholderText("Enter the element to remove") | ||
self.remove_button = QPushButton() | ||
self.remove_button.setText("Remove") | ||
self.lookup_text = QLineEdit() | ||
self.lookup_text.setPlaceholderText("Enter the element to lookup") | ||
self.lookup_button = QPushButton() | ||
self.lookup_button.setText("Lookup") | ||
|
||
layout.addWidget(self.state_label) | ||
layout.addWidget(self.info_label) | ||
|
||
layout.addWidget(self.add_text) | ||
layout.addWidget(self.add_button) | ||
layout.addWidget(self.remove_text) | ||
layout.addWidget(self.remove_button) | ||
layout.addWidget(self.lookup_text) | ||
layout.addWidget(self.lookup_button) | ||
|
||
# initial value, after connection, it is going to be either 0 or 1. | ||
self.node_id: int = -1 | ||
self.A: Set[str] = set() | ||
self.R: Set[str] = set() | ||
|
||
self.start_CRDT: bool = False | ||
self.dest_address: List[Tuple[str,int]] = [] | ||
|
||
self.add_button.clicked.connect(partial(self.update, isAdd=True)) | ||
self.remove_button.clicked.connect(partial(self.update, isAdd=False)) | ||
self.lookup_button.clicked.connect(self.lookup) | ||
|
||
|
||
def request_value(self): | ||
# user interface function | ||
self.state_label.setText(f"Current State: {self._value()}\n A: {self.A}\n R: {self.R}") | ||
|
||
def update(self, isAdd: bool): | ||
# user interface function | ||
if isAdd: | ||
self._add() | ||
self.add_text.clear() | ||
else: | ||
self._remove() | ||
self.remove_text.clear() | ||
|
||
self.request_value() | ||
|
||
def lookup(self): | ||
# user interface function | ||
lookup_value: str = self.lookup_text.text().strip() | ||
lookup_result: bool = self._lookup(lookup_value) | ||
|
||
if lookup_result: | ||
self.info_label.setText(f"{lookup_value} is in current state.") | ||
else: | ||
self.info_label.setText(f"{lookup_value} is not in current state.") | ||
|
||
def _add(self): | ||
# CRDT implementation | ||
add_value: str = self.add_text.text().strip() | ||
if add_value != "Enter the element to add": | ||
self.A.add(add_value) | ||
|
||
def _remove(self): | ||
# CRDT implementation | ||
remove_value: str = self.remove_text.text().strip() | ||
if remove_value != "Enter the element to remove": | ||
if self._lookup(remove_value): | ||
self.R.add(remove_value) | ||
|
||
def _lookup(self, lookup_value: str): | ||
# CRDT implementation | ||
isAdded: bool = lookup_value in self.A | ||
isNotRemoved: bool = lookup_value not in self.R | ||
|
||
return isAdded and isNotRemoved | ||
|
||
def _merge(self, other_set: set, isAdd: bool): | ||
# called asynchronously | ||
if isAdd: | ||
self.A = self.A.union(other_set) | ||
else: | ||
self.R = self.R.union(other_set) | ||
|
||
def _value(self): | ||
# CRDT implementation -> user interface : value | ||
return self.A - self.R | ||
|
||
def receive(self): | ||
# receive "state_vector" from another client | ||
# call merge function | ||
while True: | ||
try: | ||
message, _ = self.client.recvfrom(1024) | ||
message = message.decode(self.DATA_FORMAT) | ||
self.info_label.setText(message) | ||
if message.startswith("INFO:"): | ||
str_node_id, connection, port = message.split(":")[1].split(", ") | ||
self.node_id = int(str_node_id) | ||
self.dest_address.append((connection, int(port))) | ||
print("Got another client's information") | ||
elif len(message) > 0 and "Added set:" in message: | ||
str_added_elements: str = message.split(":")[1] | ||
set_added_elements: Set[str] = set(str_added_elements.split(",")) | ||
self._merge(other_set=set_added_elements, isAdd=True) | ||
elif len(message) > 0 and "Removed set:" in message: | ||
str_removed_elements: str = message.split(":")[1] | ||
set_removed_elements: Set[str] = set(str_removed_elements.split(",")) | ||
self._merge(other_set=set_removed_elements, isAdd=False) | ||
|
||
# update count label | ||
self.request_value() | ||
except Exception as e: | ||
self.info_label.setText(e) | ||
|
||
def start(self): | ||
# send message to the server | ||
self.client.sendto(f"SIGNUP_TAG:{self.CLIENT_PORT}".encode(self.DATA_FORMAT), self.SERVER_ADDRESS) | ||
while not self.start_CRDT: | ||
if self.node_id != -1: | ||
self.start_CRDT = True | ||
|
||
# connect with another client | ||
# send the "state_vector" to another client every 10 seconds | ||
print("start CRDT") | ||
while True: | ||
time.sleep(5) | ||
str_added_elements: str = ",".join(self.A) | ||
str_removed_elements: str = ",".join(self.R) | ||
|
||
# send to all others | ||
for each_dest_address in self.dest_address: | ||
if len(str_added_elements) != 0: | ||
self.client.sendto(f"Added set:{str_added_elements}".encode(self.DATA_FORMAT), each_dest_address) | ||
if len(str_removed_elements) != 0: | ||
self.client.sendto(f"Removed set:{str_removed_elements}".encode(self.DATA_FORMAT), each_dest_address) | ||
|
||
if __name__ == "__main__": | ||
app = QApplication(sys.argv) | ||
window = MyApp() | ||
t1 = threading.Thread(target=window.start, daemon=True) | ||
t2 = threading.Thread(target=window.receive, daemon=True) | ||
t1.start() | ||
t2.start() | ||
window.show() | ||
app.exec() |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import sys | ||
from typing import Tuple, List | ||
from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout | ||
import random | ||
import socket | ||
import threading | ||
import time | ||
from functools import partial | ||
|
||
|
||
class MyApp(QWidget): | ||
def __init__(self): | ||
super().__init__() | ||
self.setWindowTitle("Lattice Gym") | ||
self.resize(300, 300) # width, height | ||
layout = QVBoxLayout() | ||
self.setLayout(layout) | ||
|
||
# socket parameters | ||
self.SERVER_PORT: int = 1234 | ||
self.CLIENT_PORT: int = random.randint(5000, 9000) | ||
self.SERVER_NAME: str = "localhost" | ||
self.SERVER_ADDRESS: Tuple[str, int] = (self.SERVER_NAME, self.SERVER_PORT) | ||
self.CLIENT_ADDRESS: Tuple[str, int] = (self.SERVER_NAME, self.CLIENT_PORT) | ||
self.DATA_FORMAT: str = "utf-8" | ||
self.DISCONNECTION_INFO: str = "!DISCONNECT" | ||
|
||
self.client: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
self.client.bind(self.CLIENT_ADDRESS) | ||
|
||
# widgets | ||
self.count_label = QLabel() | ||
self.count_label.setText("Hello, Welcome to lattice gym!") | ||
self.debug_label = QLabel() | ||
self.debug_label.setText("Debugging Information") | ||
self.increment_button = QPushButton() | ||
self.increment_button.setText("Increment") | ||
self.decrement_button = QPushButton() | ||
self.decrement_button.setText("Decrement") | ||
|
||
layout.addWidget(self.count_label) | ||
layout.addWidget(self.debug_label) | ||
layout.addWidget(self.increment_button) | ||
layout.addWidget(self.decrement_button) | ||
|
||
# initial value, after connection, it is going to be either 0 or 1. | ||
self.node_id: int = -1 | ||
self.count_value: int = 0 | ||
self.increment_button.clicked.connect(partial(self.operation, isAdd=True)) | ||
self.decrement_button.clicked.connect(partial(self.operation, isAdd=False)) | ||
|
||
self.start_CRDT: bool = False | ||
# self.dest_address: Tuple[str,int] | ||
self.dest_address: List[Tuple[str,int]] = [] | ||
|
||
def request_value(self): | ||
# user interface function | ||
self.count_label.setText(f"Current value: {self.query()}") | ||
info: str = f"current state: ({self.query()})\n" | ||
self.debug_label.setText(info) | ||
|
||
def operation(self, isAdd: bool): | ||
message: str = self._prepare(isAdd=isAdd) | ||
self._effect(message=message) | ||
|
||
# broadcast m to other replicas | ||
for each_dest in self.dest_address: | ||
self.client.sendto(f"{message}: ".encode(self.DATA_FORMAT), each_dest) | ||
|
||
# update interface | ||
self.request_value() | ||
|
||
def query(self): | ||
return self._eval() | ||
|
||
def _prepare(self, isAdd: bool): | ||
if isAdd: | ||
return "increment" | ||
else: | ||
return "decrement" | ||
|
||
def _effect(self, message: str): | ||
if "increment" in message: | ||
self.count_value += 1 | ||
elif "decrement" in message: | ||
self.count_value -= 1 | ||
|
||
def _eval(self): | ||
return self.count_value | ||
|
||
def receive(self): | ||
# receive "state_vector" from another client | ||
# call merge function | ||
while True: | ||
try: | ||
message, _ = self.client.recvfrom(1024) | ||
message = message.decode(self.DATA_FORMAT) | ||
self.debug_label.setText(message) | ||
if message.startswith("INFO:"): | ||
str_node_id, connection, port = message.split(":")[1].split(", ") | ||
self.node_id = int(str_node_id) | ||
self.dest_address.append((connection, int(port))) | ||
print("Got another client's information") | ||
elif len(message) > 0 and "increment" in message: | ||
self._effect(message) | ||
self.request_value() | ||
elif len(message) > 0 and "decrement" in message: | ||
self._effect(message) | ||
self.request_value() | ||
except Exception as e: | ||
self.debug_label.setText(e) | ||
|
||
def start(self): | ||
# send message to the server | ||
self.client.sendto(f"SIGNUP_TAG:{self.CLIENT_PORT}".encode(self.DATA_FORMAT), self.SERVER_ADDRESS) | ||
while not self.start_CRDT: | ||
if self.node_id != -1: | ||
self.start_CRDT = True | ||
|
||
if __name__ == "__main__": | ||
app = QApplication(sys.argv) | ||
window = MyApp() | ||
t1 = threading.Thread(target=window.start, daemon=True) | ||
t2 = threading.Thread(target=window.receive, daemon=True) | ||
t1.start() | ||
t2.start() | ||
window.show() | ||
app.exec() |
Oops, something went wrong.