From 98cef5e9a772602d42acfcf233838c760424db9a Mon Sep 17 00:00:00 2001 From: Nicolas James Date: Thu, 13 Feb 2025 18:00:17 +1100 Subject: initial commit --- comp2511/blackout/EntityBase.java | 324 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 comp2511/blackout/EntityBase.java (limited to 'comp2511/blackout/EntityBase.java') 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 received_files = new Vector(); + private Vector sent_files = new Vector(); + + /** + * 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 getReceivedFiles() { + return this.received_files; + } + public Vector 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 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 getByteStoreLimit(); + /** + * Returns an optional integer limit on the number of bytes downloadable per minute. + * Returning no value means no limit. + */ + protected abstract Optional getByteDownloadSpeed(); + /** + * Returns an optional integer limit on the number of bytes uploadable per minute. + * Returning no value means no limit. + */ + protected abstract Optional 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 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 getDownloadBandwidth() { + Optional 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 getUploadBandwidth() { + Optional 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 a, Optional 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 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 upload_bandwidth = this.getUploadBandwidth(); + Optional 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 entities, Vector 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 entities) { + Vector visited = new Vector(); + return canCommunicateSearch(target, entities, visited); + } + + /** + * Helper to extract files via their filenames, returns an empty optional if it does not exist. + */ + static private Optional getFileFromFilename(String filename, Vector 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 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 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 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 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 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 -- cgit v1.2.3