aboutsummaryrefslogtreecommitdiff
path: root/comp2511/blackout/EntityBase.java
diff options
context:
space:
mode:
authorNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-13 18:00:17 +1100
committerNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-13 18:00:17 +1100
commit98cef5e9a772602d42acfcf233838c760424db9a (patch)
tree5277fa1d7cc0a69a0f166fcbf10fd320f345f049 /comp2511/blackout/EntityBase.java
initial commit
Diffstat (limited to 'comp2511/blackout/EntityBase.java')
-rw-r--r--comp2511/blackout/EntityBase.java324
1 files changed, 324 insertions, 0 deletions
diff --git a/comp2511/blackout/EntityBase.java b/comp2511/blackout/EntityBase.java
new file mode 100644
index 0000000..33241d9
--- /dev/null
+++ b/comp2511/blackout/EntityBase.java
@@ -0,0 +1,324 @@
+package unsw.blackout;
+
+import unsw.utils.Angle;
+
+import java.util.Optional;
+import java.util.Vector;
+
+public abstract class EntityBase {
+ private String id;
+ private Angle position;
+ private double height;
+ private Vector<File> received_files = new Vector<File>();
+ private Vector<File> sent_files = new Vector<File>();
+
+ /**
+ * BaseEntity is a class that contains an ID and a type which represents any object in our network.
+ */
+ EntityBase(String id, Angle position, double height) {
+ this.id = id;
+ this.position = position;
+ this.height = height;
+ }
+
+ // Getters.
+ public String getId() {
+ return this.id;
+ }
+ public Angle getAngle() {
+ return this.position;
+ }
+ public double getHeight() {
+ return this.height;
+ }
+ public Vector<File> getReceivedFiles() {
+ return this.received_files;
+ }
+ public Vector<File> getSentFiles() {
+ return this.sent_files;
+ }
+ public void setAngle(Angle angle) {
+ this.position = angle;
+ }
+
+ /**
+ * Returns the velocity of the entity in km/s.
+ */
+ public abstract double getVelocity();
+ /**
+ * Returns the range of the entity in km.
+ */
+ public abstract double getRange();
+ /**
+ * Returns a boolean flagging if the entity may communicate with the other entity.
+ * Warning: it is possible that one entity may allow, while the other does not.
+ */
+ protected abstract boolean isSupportedEntity(EntityBase entity);
+ /**
+ * Returns a boolean flagging if the entity is physically in range and unobstructed
+ * to another entity.
+ */
+ protected abstract boolean isPhysicallyReachable(EntityBase entity);
+ /**
+ * Returns a boolean flagging if the entity may retransmit messages to other entities.
+ */
+ protected boolean canRelay() {
+ return false;
+ }
+ /**
+ * Returns a boolean flagging if the entity should NOT delete files when out of range.
+ */
+ protected boolean canTransient() {
+ return false;
+ }
+ /**
+ * Returns a boolean flagging if the entity may shrink files.
+ */
+ protected boolean canShrink() {
+ return false;
+ }
+ /**
+ * Returns an optional integer limit on the number of files it may hold.
+ * Returning no value means no limit.
+ */
+ protected abstract Optional<Integer> getFileStoreLimit();
+ /**
+ * Returns an optional integer limit on the number of bytes it may hold across all files.
+ * Returning no value means no limit.
+ */
+ protected abstract Optional<Integer> getByteStoreLimit();
+ /**
+ * Returns an optional integer limit on the number of bytes downloadable per minute.
+ * Returning no value means no limit.
+ */
+ protected abstract Optional<Integer> getByteDownloadSpeed();
+ /**
+ * Returns an optional integer limit on the number of bytes uploadable per minute.
+ * Returning no value means no limit.
+ */
+ protected abstract Optional<Integer> getByteUploadSpeed();
+
+ /**
+ * Helper function to calculate an offset to the position class in radians.
+ */
+ protected double getMoveOffsetRadians() {
+ return (this.getVelocity() / this.getHeight());
+ }
+ /**
+ * Moves the entity to the expected position after 1 minute.
+ */
+ public void move() {
+ double offset = this.getMoveOffsetRadians();
+ double rposition = this.position.toRadians() + offset;
+ this.position = Angle.fromRadians(rposition % Math.toRadians(360.0));
+ }
+
+ /**
+ * Helper function, returns the number of files that have not been fully transmitted in the vector.
+ */
+ static private int getNumUncompletedFiles(Vector<File> files) {
+ int count = 0;
+ for (File file : files) {
+ if (file.hasFullyTransmitted()) {
+ continue;
+ }
+ ++count;
+ }
+ return count;
+ }
+
+ /**
+ * Gets the number of bytes in received files.
+ */
+ protected int getNumBytesInFiles() {
+ int count = 0;
+ for (File file : this.received_files) {
+ count += file.getContents().length();
+ }
+ return count;
+ }
+
+ /**
+ * Returns the speed a file should be downloaded per tick for fairness during multiple transfers.
+ * Empty optional means infinite value.
+ */
+ private Optional<Integer> getDownloadBandwidth() {
+ Optional<Integer> download_speed = this.getByteDownloadSpeed();
+ if (download_speed.isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.of(download_speed.get() / EntityBase.getNumUncompletedFiles(this.getReceivedFiles()));
+ }
+ /**
+ * Returns the speed a file should be uploaded per tick for fairness during multiple transfers.
+ * Empty optional means infinite value.
+ */
+ private Optional<Integer> getUploadBandwidth() {
+ Optional<Integer> upload_speed = this.getByteUploadSpeed();
+ if (upload_speed.isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.of(upload_speed.get() / EntityBase.getNumUncompletedFiles(this.getSentFiles()));
+ }
+
+ /**
+ * Heper function, returns the min of two optional integers, or a concrete value representing infinity.
+ */
+ static private int getOptionalMin(Optional<Integer> a, Optional<Integer> b) {
+ if (a.isPresent() && b.isPresent()) {
+ return Math.min(a.get(), b.get());
+ } else if (a.isPresent()) {
+ return a.get();
+ } else if (b.isPresent()) {
+ return b.get();
+ }
+ return Integer.MAX_VALUE;
+ }
+ public void removeSentFiles(Vector<File> files) {
+ for (File file : files) {
+ getSentFiles().remove(file);
+ }
+ }
+ /**
+ * Transfers currently sending files by the expected values after 1 minute.
+ * Returns false if the file should be deleted.
+ */
+ public boolean maybeTransferFile(File file, EntityBase target) {
+ // Delete and early out files if we are out of range - but keep if transient!
+ if (!this.isPhysicallyReachable(target)) {
+ if (!target.canTransient()) {
+ target.getReceivedFiles().remove(file);
+ return false;
+ }
+ return true;
+ }
+
+ Optional<Integer> upload_bandwidth = this.getUploadBandwidth();
+ Optional<Integer> download_bandwidth = target.getDownloadBandwidth();
+
+ // The amount we can send is the min of our upload bandwidth and their download bandwidth.
+ int bandwidth = EntityBase.getOptionalMin(upload_bandwidth, download_bandwidth);
+ file.addBytes(bandwidth);
+ return true;
+ }
+
+ /**
+ * Recursive DPS helper function to determine whether an entity may communicate with another.
+ */
+ private boolean canCommunicateSearch(EntityBase target, Vector<EntityBase> entities, Vector<EntityBase> visited) {
+ visited.add(this);
+ for (EntityBase entity : entities) {
+ if (entity.equals(this)) {
+ continue;
+ }
+
+ if (visited.contains(entity)) {
+ continue;
+ }
+
+ if (!this.isSupportedEntity(entity) || !entity.isSupportedEntity(this)) {
+ continue;
+ }
+
+ if (!this.isPhysicallyReachable(entity)) {
+ continue;
+ }
+
+ if (target.equals(entity)) {
+ return true;
+ }
+
+ if (entity.canRelay()) {
+ if (entity.canCommunicateSearch(target, entities, visited)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ /**
+ * Determines whether an entity may communicate with another entity, including via hops from other entities.
+ */
+ public boolean canCommunicate(EntityBase target, Vector<EntityBase> entities) {
+ Vector<EntityBase> visited = new Vector<EntityBase>();
+ return canCommunicateSearch(target, entities, visited);
+ }
+
+ /**
+ * Helper to extract files via their filenames, returns an empty optional if it does not exist.
+ */
+ static private Optional<File> getFileFromFilename(String filename, Vector<File> files) {
+ for (File file : files) {
+ if (file.getFilename().equals(filename)) {
+ return Optional.of(file);
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Sends a file to the entity, while respecting the constraints imposed by both entities.
+ */
+ public void sendFileTo(String filename, EntityBase target) throws FileTransferException {
+ Optional<File> origin_file = EntityBase.getFileFromFilename(filename, this.getReceivedFiles());
+
+ if (origin_file.isEmpty()) { // throw if the file does not exist
+ throw new FileTransferException.VirtualFileNotFoundException(filename);
+ }
+ if (!origin_file.get().hasFullyTransmitted()) { // throw if the file has not fully downloaded
+ throw new FileTransferException.VirtualFileNotFoundException(filename);
+ }
+
+ // throw if the file already exist or is currently downloading, nicely handles both here
+ if (EntityBase.getFileFromFilename(filename, target.getReceivedFiles()).isPresent()) {
+ throw new FileTransferException.VirtualFileAlreadyExistsException(filename);
+ }
+
+ Optional<Integer> upload_speed = this.getByteUploadSpeed(); // throw if we don't have enough bandwidth
+ if (upload_speed.isPresent() && EntityBase.getNumUncompletedFiles(this.sent_files) >= upload_speed.get()) {
+ throw new FileTransferException.VirtualFileNoBandwidthException(this.getId());
+ }
+
+ Optional<Integer> download_speed = target.getByteDownloadSpeed(); // throw if they don't have enough bandwidth
+ if (download_speed.isPresent() && EntityBase.getNumUncompletedFiles(target.getReceivedFiles()) >= download_speed.get()) {
+ throw new FileTransferException.VirtualFileNoBandwidthException(target.getId());
+ }
+
+ // Throw if they can't store any more files.
+ Optional<Integer> download_file_limit = target.getFileStoreLimit();
+ if (download_file_limit.isPresent() && target.getReceivedFiles().size() >= download_file_limit.get()) {
+ throw new FileTransferException.VirtualFileNoStorageSpaceException("Max Files Reached");
+ }
+
+ // Throw if they don't have enough space.
+ Optional<Integer> download_byte_limit = target.getByteStoreLimit();
+ int filesize = origin_file.get().getContents().length();
+ if (download_byte_limit.isPresent() && target.getNumBytesInFiles() + filesize >= download_byte_limit.get()) {
+ throw new FileTransferException.VirtualFileNoStorageSpaceException("Max Storage Reached");
+ }
+
+ // Finally we upload the file.
+ String contents = origin_file.get().getContents();
+ File upload_file = new File(filename, contents, 0, target.getId());
+ this.getSentFiles().add(upload_file);
+ target.getReceivedFiles().add(upload_file);
+ }
+
+ // We know that our ID is unique, so that's the only thing we need to compare here.
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ EntityBase other = (EntityBase) obj;
+ if (id == null) {
+ if (other.id != null)
+ return false;
+ } else if (!id.equals(other.id))
+ return false;
+ return true;
+ }
+} \ No newline at end of file