diff --git a/src/edu/nps/moves/dis7/utilities/DisChannel.java b/src/edu/nps/moves/dis7/utilities/DisChannel.java new file mode 100644 index 0000000000000000000000000000000000000000..a9b0b99fe87cdd0c7e9d68f46a2557cef16fce45 --- /dev/null +++ b/src/edu/nps/moves/dis7/utilities/DisChannel.java @@ -0,0 +1,380 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package edu.nps.moves.dis7.utilities; + +import edu.nps.moves.dis7.enumerations.VariableRecordType; +import edu.nps.moves.dis7.pdus.CommentPdu; +import edu.nps.moves.dis7.pdus.EntityID; +import edu.nps.moves.dis7.pdus.Pdu; +import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface; +import edu.nps.moves.dis7.utilities.DisTime; +import edu.nps.moves.dis7.utilities.PduFactory; +import edu.nps.moves.dis7.utilities.stream.PduRecorder; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +// import jdk.internal.vm.annotation.IntrinsicCandidate; + +/** + * DisChannel integrates multiple utility capabilities to handle most networking and entity-management tasks. + * Provides a simplified interface wrapping DisThreadedNetworkInterface, PduRecorder and SimulationManager + * for programs connecting to OpenDis7 communications. + * TODO future work will confirm that multiple different DisChannel connections can be used simultaneously by a parent program. + * @author brutzman + */ +public class DisChannel +{ + private String descriptor = this.getClass().getSimpleName(); + /** + * Output prefix to help with logging by identifying this class. + */ + // might have different DisChannel objects created on different channels, so TRACE_PREFIX is non-static + private String TRACE_PREFIX = "[" + descriptor + "] "; + private static String thisHostName = "localhost"; + private static final String NETWORK_ADDRESS_DEFAULT = "239.1.2.3"; + private static final int NETWORK_PORT_DEFAULT = 3000; + private static final String DEFAULT_PDULOG_OUTPUT_DIRECTORY = "./pduLog"; + + protected boolean verboseComments = true; + String networkAddress = NETWORK_ADDRESS_DEFAULT; + int networkPort = NETWORK_PORT_DEFAULT; + static DisTime.TimestampStyle timestampStyle = DisTime.TimestampStyle.IEEE_ABSOLUTE; + + /** Creates DIS Protocol Data Unit (PDU) classes for simulation entities */ + private static PduFactory pduFactory; + + // class variables + private DisThreadedNetworkInterface disNetworkInterface; + DisThreadedNetworkInterface.PduListener pduListener; + Pdu receivedPdu; + private PduRecorder pduRecorder; + + /* VariableRecordType enumerations have potential use with CommentPdu logs */ + /* TODO contrast to EntityType */ + public final VariableRecordType descriptionCommentType = VariableRecordType.DESCRIPTION; + public final VariableRecordType narrativeCommentType = VariableRecordType.COMPLETE_EVENT_REPORT; + public final VariableRecordType statusCommentType = VariableRecordType.APPLICATION_STATUS; + public final VariableRecordType currentTimeStepCommentType = VariableRecordType.APPLICATION_TIMESTEP; + + /** SimulationManager class handles DIS joining, announcing and leaving tasks. + * It is instantiated here as an object */ + SimulationManager simulationManager = new SimulationManager(); + + /** Base constructor */ + public DisChannel() + { + // base constructor is not invoked automatically by other constructors + // https://stackoverflow.com/questions/581873/best-way-to-handle-multiple-constructors-in-java + + initialize(); + } + /** Constructor with new descriptor + * @param newDescriptor descriptor for this instance */ + public DisChannel(String newDescriptor) + { + descriptor = newDescriptor; + initialize(); + } + /** Initialize this class */ + private void initialize() + { + DisTime.setTimestampStyle(timestampStyle); // DISTime is a singleton shared class + pduFactory = new PduFactory(timestampStyle); + + try + { + thisHostName = InetAddress.getLocalHost().getHostName(); + printlnTRACE("thisHostName=" + thisHostName); + } + catch (UnknownHostException uhe) + { + printlnTRACE(thisHostName + " is not connected to network: " + uhe.getMessage()); + } + } + /** add entity using SimulationManager + * @param newEntity new entity to add for announcement by SimulationManager */ + public void addEntity(EntityID newEntity) + { + // TODO send simulation management PDUs + simulationManager.addEntity(newEntity); + } + + /** Join DIS channel using SimulationManager */ + public void join() + { + // TODO simulation management PDUs for startup, planning to design special class support +// simulationManager.addEntity(); + simulationManager.setDescriptor(descriptor); + simulationManager.addHost(getThisHostName()); + simulationManager.setDisThreadedNetworkInterface(disNetworkInterface); + + simulationManager.simulationJoin(); + simulationManager.simulationStart(); + // TODO consider boolean response indicating if join was successful + } + /** Leave DIS channel using SimulationManager */ + public void leave() + { + // TODO send simulation management PDUs + simulationManager.simulationStop(); + simulationManager.simulationLeave(); + // TODO consider boolean response indicating if leave was successful + } + + /** + * get current networkAddress as a string + * @return the networkAddress + */ + public String getNetworkAddress() { + return networkAddress; + } + + /** + * set current networkAddress using a string + * @param newNetworkAddress the networkAddress to set + */ + public final void setNetworkAddress(String newNetworkAddress) { + this.networkAddress = newNetworkAddress; + } + + /** + * get current networkPort + * @return the networkPort + */ + public int getNetworkPort() { + return networkPort; + } + + /** + * set current networkPort + * @param newNetworkPort the networkPort to set + */ + public final void setNetworkPort(int newNetworkPort) { + this.networkPort = newNetworkPort; + } + + /** + * Get timestampStyle used by PduFactory + * @return current timestampStyle + */ + public DisTime.TimestampStyle getTimestampStyle() { + return timestampStyle; + } + + /** + * Set timestampStyle used by PduFactory + * @param newTimestampStyle the timestampStyle to set + */ + public void setTimestampStyle(DisTime.TimestampStyle newTimestampStyle) { + timestampStyle = newTimestampStyle; + DisTime.setTimestampStyle(newTimestampStyle); + } + + /** + * Initialize network interface, choosing best available network interface + */ + public void setUpNetworkInterface() + { + if (disNetworkInterface != null) + { + printlnTRACE("*** Warning: setUpNetworkInterface() has already created disNetworkInterface, second invocation ignored"); + return; + } + disNetworkInterface = new DisThreadedNetworkInterface(getNetworkAddress(), getNetworkPort()); + getDisNetworkInterface().setDescriptor(descriptor); + printlnTRACE("Network confirmation:" + " address=" + getDisNetworkInterface().getAddress() + // disNetworkInterface.getMulticastGroup() + + " port=" + getDisNetworkInterface().getPort()); // + disNetworkInterface.getDisPort()); + pduListener = new DisThreadedNetworkInterface.PduListener() { + /** Callback handler for listener */ + @Override + public void incomingPdu(Pdu newPdu) { + receivedPdu = newPdu; + } + }; + getDisNetworkInterface().addListener(pduListener); + String pduLogOutputDirectory = DEFAULT_PDULOG_OUTPUT_DIRECTORY; + printlnTRACE("Beginning pdu save to directory " + pduLogOutputDirectory); + pduRecorder = new PduRecorder(pduLogOutputDirectory, getNetworkAddress(), getNetworkPort()); // assumes save + pduRecorder.setEncodingPduLog(PduRecorder.ENCODING_PLAINTEXT); + pduRecorder.setVerbose(true); // either sending, receiving or both + pduRecorder.start(); // begin running + } + + /** All done, release network resources */ + public void tearDownNetworkInterface() { + getPduRecorder().stop(); // handles disNetworkInterface.close(), tears down threads and sockets + } + + /** + * Send a single Protocol Data Unit (PDU) of any type + * @param pdu the pdu to send + */ + protected void sendSinglePdu(Pdu pdu) + { + if (getDisNetworkInterface() == null) + setUpNetworkInterface(); // ensure connected + try + { + getDisNetworkInterface().send(pdu); + Thread.sleep(100); // TODO consider refactoring the wait logic and moving externally + } + catch (InterruptedException ex) + { + System.err.println(this.getClass().getSimpleName() + " Error sending PDU: " + ex.getLocalizedMessage()); + System.exit(1); + } + } + /** + * Send Comment PDU + * @see <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">Passing Information to a Method or a Constructor</a> Arbitrary Number of Arguments + * @param commentType enumeration value describing purpose of the narrative comment + * @param comments String array of narrative comments + */ + public void sendCommentPdu(VariableRecordType commentType, + // vararg... variable-length set of String comments can optionally follow + String... comments) + { + if ((comments != null) && (comments.length > 0)) + { + ArrayList<String> newCommentsList = new ArrayList<>(); + for (String comment : comments) + { + if (!comment.isEmpty()) + { + newCommentsList.add(comment); // OK found something to send + } + } + if (!newCommentsList.isEmpty()) + { + if (getDisNetworkInterface() == null) + setUpNetworkInterface(); // ensure connected + + if (commentType == null) + commentType = VariableRecordType.OTHER; // fallback value; TODO consider pushing into pduFactory + // now build the commentPdu from these string inputs, thus constructing a narrative entry + @SuppressWarnings("CollectionsToArray") + CommentPdu commentPdu = getPduFactory().makeCommentPdu(commentType, newCommentsList.toArray(new String[0])); // comments); + sendSinglePdu(commentPdu); + if (isVerboseComments()) + { + printlnTRACE("*** [CommentPdu narrative sent: " + commentType.name() + "] " + newCommentsList.toString()); + System.out.flush(); + } + } + } + } + + /** + * test for verboseComments mode + * @return whether verboseComments mode is enabled + */ + public boolean isVerboseComments() { + return verboseComments; + } + + /** + * set verboseComments mode + * @param newVerboseComments whether verboseComments mode is enabled + */ + public void setVerboseComments(boolean newVerboseComments) { + this.verboseComments = newVerboseComments; + } + + /** + * @return the TRACE_PREFIX + */ + public String getTRACE_PREFIX() { + return TRACE_PREFIX; + } + + /** + * @param newTRACE_PREFIX the TRACE_PREFIX to set + */ + public final void setTRACE_PREFIX(String newTRACE_PREFIX) { + if (newTRACE_PREFIX == null) + newTRACE_PREFIX = ""; + if (newTRACE_PREFIX.isBlank()) + TRACE_PREFIX = "[" + DisThreadedNetworkInterface.class.getSimpleName() + "] "; + else if (newTRACE_PREFIX.contains(this.getClass().getSimpleName())) + TRACE_PREFIX = "[" + newTRACE_PREFIX + "] "; + else TRACE_PREFIX = "[" + this.getClass().getSimpleName() + " " + newTRACE_PREFIX + "] "; + } + + /** + * Print message with TRACE_PREFIX prepended + * @param message String to print + */ + public void printTRACE(String message) { + System.out.print(TRACE_PREFIX + message); + } + /** + * Print message with TRACE_PREFIX prepended + * @param message String to print + */ + public void printlnTRACE(String message) { + System.out.println(TRACE_PREFIX + message); + } + + /** + * @return the pduFactory, simplifying program imports and configuration + */ + public PduFactory getPduFactory() { + if (pduFactory == null) + pduFactory = new PduFactory(timestampStyle); + return pduFactory; + } + + /** + * @return the disNetworkInterface + */ + public DisThreadedNetworkInterface getDisNetworkInterface() { + return disNetworkInterface; + } + + /** + * @return the thisHostName + */ + public static String getThisHostName() { + return thisHostName; + } + + /** + * @param aThisHostName the thisHostName to set + */ + public static void setThisHostName(String aThisHostName) { + thisHostName = aThisHostName; + } + + /** + * @return the pduRecorder + */ + public PduRecorder getPduRecorder() { + return pduRecorder; + } + + /** + * Get simple descriptor (such as parent class name) for this network interface, used in trace statements + * @return simple descriptor name + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Set new simple descriptor (such as parent class name) for this network interface, used in trace statements + * @param newDescriptor simple descriptor name for this interface + */ + public void setDescriptor(String newDescriptor) { + // might have different DisChannel objects created on different channels, so descriptor is non-static + if (newDescriptor == null) + newDescriptor = ""; + this.descriptor = newDescriptor; + setTRACE_PREFIX(descriptor); + if (disNetworkInterface != null) + disNetworkInterface.setDescriptor(descriptor); + if (simulationManager != null) + simulationManager.setDescriptor(descriptor); + } +} diff --git a/src/edu/nps/moves/dis7/utilities/SimulationManager.java b/src/edu/nps/moves/dis7/utilities/SimulationManager.java new file mode 100644 index 0000000000000000000000000000000000000000..b4fef60b18fc8dc85790d5ae675f1d8d670c38cc --- /dev/null +++ b/src/edu/nps/moves/dis7/utilities/SimulationManager.java @@ -0,0 +1,703 @@ +/* +Copyright (c) 1995-2022 held by the author(s). All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the names of the Naval Postgraduate School (NPS) + Modeling Virtual Environments and Simulation (MOVES) Institute + https://www.nps.edu and https://www.nps.edu/web/moves + nor the names of its contributors may be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +// TODO move into opendis7 distribution tree + +package edu.nps.moves.dis7.utilities; + +import edu.nps.moves.dis7.pdus.CreateEntityPdu; +import edu.nps.moves.dis7.pdus.EntityID; +import edu.nps.moves.dis7.pdus.RemoveEntityPdu; +import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface; +import edu.nps.moves.dis7.utilities.DisTime; +import java.util.ArrayList; + +/** + * Manage overall Simulation Management (SIMAN) choreography for a DIS channel participant. + * TODO once operation is working satisfactorily, this class will be moved into the opendis7-java distribution utilities. + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/specifications/README.md" target="_blank">Networked Graphics MV3500, Specification Documents, IEEE and SISO</a> + * @see <a href="https://ieeexplore.ieee.org/document/6387564" target="_blank">1278.1-2012. IEEE Standard for Distributed Interactive Simulation (DIS) - Application Protocols</a> 5.6.3 The simulation management computer + * @see <a href="https://ieeexplore.ieee.org/document/587529" target="_blank">1278.3-1996. IEEE Recommended Practice for Distributed Interactive Simulation - Exercise Management and Feedback</a> + * @see IEEE 1278.1 DIS Application Protocols, 4.6.3.a.5 Timestamp, General Requirements, page 43 + * @see IEEE 1278.1 DIS Application Protocols, 4.6.3.c.1 Timestamp, Relative Timestamps, page 44 + * @see IEEE 1278.1 DIS Application Protocols, 5.6 Simulation management, page 89 + * @author brutzman + */ +public class SimulationManager +{ + private DisThreadedNetworkInterface disThreadedNetworkInterface; + private static ArrayList<RecordType> entityRecordList = new ArrayList<>(); + private static ArrayList<RecordType> hostRecordList = new ArrayList<>(); + private static ArrayList<RecordType> applicationRecordList = new ArrayList<>(); + private String descriptor = new String(); + private static int hostID = 0; + + private String TRACE_PREFIX = "[" + (SimulationManager.class.getSimpleName()) + "] "; + + /** + * Object constructor with descriptor + * @param newDescriptor simple descriptor name for this class + */ + public SimulationManager (String newDescriptor) + { + if (newDescriptor != null) + descriptor = newDescriptor.trim(); + else descriptor = ""; + } + /** + * Object constructor + */ + public SimulationManager () + { + this(""); + } + + /** + * Start the simulation according to specifications + */ + public void simulationStart() + { + // TODO + } + /** + * Pause the simulation according to specifications + */ + public void simulationPause() + { + // TODO + } + /** + * Resume the simulation according to specifications + */ + public void simulationResume() + { + // TODO + } + /** + * Stop the simulation according to specifications + */ + public void simulationStop() + { + // TODO + } + /** + * An entity can Join the simulation according to specifications + */ + public void simulationJoin() + { + CreateEntityPdu createEntityPdu = new CreateEntityPdu(); + createEntityPdu.setExerciseID(123); // TODO +// createEntityPdu.setPduStatus(); // TODO + + if (hasDisThreadedNetworkInterface()) + { + for (RecordType entity : entityRecordList) + { + // TODO set record parameters + createEntityPdu.setExerciseID(entity.getId()); + createEntityPdu.setTimestamp(DisTime.getCurrentDisTimestamp()); + disThreadedNetworkInterface.send(createEntityPdu); + } + } + else + { + System.err.println(TRACE_PREFIX + "addEntity() unable to send CreateEntityPdu since no disThreadedNetworkInterface found"); + // TODO consider queue for unsent entities + } + } + /** + * An entity can Leave the simulation according to specifications + */ + public void simulationLeave() + { + // TODO + } + + /** + * Simple simulation record type + */ + public class RecordType + { + private int id = -1; + private String name = new String(); + private String alias = new String(); + private String description = new String(); + private String reference = new String(); + private boolean isHostType = false; + + /** + * Constructor for new record + * @param id identifying number + * @param name common name + * @param description longer description + * @param reference formal reference for this record, if any + */ + public RecordType (int id, String name, String description, String reference) + { + this.id = id; + this.name = name; + this.description = description; + this.reference = reference; + // TODO create alias: if IP address then check for hostname, and vice versa + } + /** + * Utility constructor for new record, description and reference remain blank + * @param id identifying number + * @param name common name + */ + public RecordType (int id, String name) + { + this.id = id; + this.name = name; + this.description = ""; + this.reference = ""; + // TODO create alias: if IP address then check for hostname, and vice versa + } + /** + * Utility constructor for new record, description and reference remain blank + * @param id identifying number + * @param name common name + * @param isHostType whether or not this record is for a host + */ + public RecordType (int id, String name, boolean isHostType) + { + this.id = id; + this.name = name; + this.description = ""; + this.reference = ""; + this.isHostType = isHostType; + // TODO create alias: if IP address then check for hostname, and vice versa + } + + /** + * Simple representation of record + * @return id,name,"description" + */ + @Override + public String toString() + { + return "id" + "," + name + ",\"" + description + "\""; + } + + /** + * get record id + * @return the id + */ + public int getId() { + return id; + } + + /** + * set record id + * @param newID the id to set + * @return same object to permit progressive setters + */ + public RecordType setId(int newID) { + this.id = newID; + return this; + } + + /** + * get record name + * @return the name + */ + public String getName() { + return name; + } + + /** + * set record name + * @param newName the name to set + * @return same object to permit progressive setters + */ + public RecordType setName(String newName) { + this.name = newName; + return this; + } + + /** + * get record description + * @return the description + */ + public String getDescription() { + return description; + } + + /** + * set record description + * @param newDescription the description to set + * @return same object to permit progressive setters + */ + public RecordType setDescription(String newDescription) { + this.description = newDescription; + return this; + } + + /** + * get record reference + * @return the reference + */ + public String getReference() { + return reference; + } + + /** + * set record reference + * @param newReference the reference to set + * @return same object to permit progressive setters + */ + public RecordType setReference(String newReference) { + this.reference = newReference; + return this; + } + + /** + * get record alias name + * @return the alias + */ + public String getAlias() { + return alias; + } + + /** + * set record alias name + * @param alias the alias to set + * @return same object to permit progressive setters + */ + public RecordType setAlias(String alias) { + this.alias = alias; + return this; + } + + /** + * Does record represent a network address + * @return whether record is a network address + */ + public boolean isNetworkAddress() { + return isHostType; + } + + /** + * Set whether record represents a network address + * @param isAddress the isAddress to set + */ + public void setNetworkAddress(boolean isAddress) { + this.isHostType = isAddress; + } + } + + /** + * Get a single entityRecord from list + * @param index which record to retrieve + * @return the record matching this index + */ + public RecordType getEntityRecordByIndex(int index) + { + if (entityRecordList.isEmpty()) + { + System.err.println ("*** getEntityRecordByIndex list is empty, unable to get index=" + index); + return null; + } + else if (entityRecordList.size() <= index) + { + System.err.println ("*** getEntityRecordByIndex list has size=" + entityRecordList.size() + ", unable to get index=" + index); + return null; + } + else if (index < 0) + { + System.err.println ("*** getEntityRecordByIndex cannot retrieve illegal index=" + index); + return null; + } + else return entityRecordList.get(index); + } + + /** + * Get a single hostRecord from list + * @param index which record to retrieve + * @return the record matching this index + */ + public RecordType getHostRecordByIndex(int index) + { + if (hostRecordList.isEmpty()) + { + System.err.println ("*** getHostRecordByIndex list is empty, unable to get index=" + index); + return null; + } + else if (hostRecordList.size() <= index) + { + System.err.println ("*** getHostRecordByIndex list has size=" + hostRecordList.size() + ", unable to get index=" + index); + return null; + } + else if (index < 0) + { + System.err.println ("*** getHostRecordByIndex cannot retrieve illegal index=" + index); + return null; + } + else return hostRecordList.get(index); + } + + /** + * Get a single applicationRecord from list + * @param index which record to retrieve + * @return the record matching this index + */ + public RecordType getApplicationRecordByIndex(int index) + { + if (applicationRecordList.isEmpty()) + { + System.err.println ("*** getApplicationRecordByIndex list is empty, unable to get index=" + index); + return null; + } + else if (applicationRecordList.size() <= index) + { + System.err.println ("*** getApplicationRecordByIndex list has size=" + applicationRecordList.size() + ", unable to get index=" + index); + return null; + } + else if (index < 0) + { + System.err.println ("*** getApplicationRecordByIndex cannot retrieve illegal index=" + index); + return null; + } + else return applicationRecordList.get(index); + } + + /** + * Get a single entityRecord from list matching ID + * @param valueOfInterest id for record to retrieve + * @return the record matching this ID + */ + public RecordType getEntityRecordByID(int valueOfInterest) + { + for (RecordType entity : entityRecordList) + { + if (entity.getId() == valueOfInterest) + return entity; + } + System.err.println ("*** getEntityRecordByID cannot find id=" + valueOfInterest); + return null; + } + /** + * Get a single hostRecord from list matching ID + * @param valueOfInterest id for record to retrieve + * @return the record matching this ID + */ + public RecordType getHostRecordByID(int valueOfInterest) + { + for (RecordType host : hostRecordList) + { + if (host.getId() == valueOfInterest) + return host; + } + System.err.println ("*** getHostRecordByID cannot find id=" + valueOfInterest); + return null; + } + /** + * Get a single applicationRecord from list matching ID + * @param valueOfInterest id for record to retrieve + * @return the record matching this ID + */ + public RecordType getApplicationRecordByID(int valueOfInterest) + { + for (RecordType application : applicationRecordList) + { + if (application.getId() == valueOfInterest) + return application; + } + System.err.println ("*** getApplicationRecordByID cannot find id=" + valueOfInterest); + return null; + } + + /** + * Provide entire entityRecordList + * @return the entityRecordList + */ + public ArrayList<RecordType> getEntityRecordList() { + return entityRecordList; + } + + /** + * Provide entire hostRecordList + * @return the hostRecordList + */ + public ArrayList<RecordType> getHostRecordList() { + return hostRecordList; + } + + /** + * Provide entire applicationRecordList + * @return the applicationRecordList + */ + public ArrayList<RecordType> getApplicationRecordList() { + return applicationRecordList; + } + + /** + * Provide access to current disThreadedNetworkInterface + * @return the disThreadedNetworkInterface + */ + protected DisThreadedNetworkInterface getDisThreadedNetworkInterface() { + return disThreadedNetworkInterface; + } + /** + * Set the disThreadedNetworkInterface singleton to match other classes + * @param disThreadedNetworkInterface the disThreadedNetworkInterface to set + * @return same object to permit progressive setters + */ + public SimulationManager setDisThreadedNetworkInterface(DisThreadedNetworkInterface disThreadedNetworkInterface) { + this.disThreadedNetworkInterface = disThreadedNetworkInterface; + return this; + } + /** + * Check for disThreadedNetworkInterface + * @return whether singleton disThreadedNetworkInterface has been instantiated + */ + protected boolean hasDisThreadedNetworkInterface() + { + return (this.disThreadedNetworkInterface != null); + } + /** + * Create disThreadedNetworkInterface + */ + protected void createDisThreadedNetworkInterface() + { + this.disThreadedNetworkInterface = new DisThreadedNetworkInterface(descriptor); + } + /** + * Constructor for disThreadedNetworkInterface with descriptor, + * using default multicast address and port + * @param newDescriptor simple descriptor name for this interface + */ + protected void createDisThreadedNetworkInterface(String newDescriptor) + { + this.disThreadedNetworkInterface = new DisThreadedNetworkInterface(newDescriptor); + } + /** + * Constructor for disThreadedNetworkInterface using specified multicast address and port + * @param address the multicast group or unicast address to utilize + * @param port the multicast port to utilize + */ + protected void createDisThreadedNetworkInterface(String address, int port) + { + this.disThreadedNetworkInterface = new DisThreadedNetworkInterface(address, port, descriptor); + } + /** + * Constructor for disThreadedNetworkInterface using specified multicast address and port, plus descriptor. + * @param address the multicast group or unicast address to utilize + * @param port the multicast port to utilize + * @param newDescriptor simple descriptor name for this interface + */ + protected void createDisThreadedNetworkInterface(String address, int port, String newDescriptor) + { + this.disThreadedNetworkInterface = new DisThreadedNetworkInterface(address, port, newDescriptor); + } + /** + * Get simple descriptor (such as parent class name) for this SimulationManager, used in trace statements + * @return simple descriptor name + */ + public String getDescriptor() + { + return descriptor; + } + /** + * Set new simple descriptor (such as parent class name) for this SimulationManager, used in trace statements + * @param newDescriptor simple descriptor name for this interface + * @return same object to permit progressive setters */ + public SimulationManager setDescriptor(String newDescriptor) + { + if (newDescriptor != null) + this.descriptor = newDescriptor.trim(); + TRACE_PREFIX = "[" + DisThreadedNetworkInterface.class.getSimpleName() + " " + descriptor + "] "; + return this; + } + /** + * Reset descriptor + * @return same object to permit progressive setters */ + public SimulationManager clearDescriptor() + { + setDescriptor(""); + return this; + } + /** + * clear all lists + * @return same object to permit progressive setters */ + public SimulationManager clearAll() + { + entityRecordList.clear(); + hostRecordList.clear(); + applicationRecordList.clear(); + clearDescriptor(); + return this; + } + /** + * Add entity to simulation list, if this is first occurrence + * @param newEntityID new entity to add + * @return same object to permit progressive setters */ + public SimulationManager addEntity(EntityID newEntityID) + { + RecordType newEntity = new RecordType(newEntityID.getEntityID(), // short + "TODOname", + "TODO description", + "TODO reference"); + entityRecordList.add(newEntity); + return this; + } + /** + * Add entity to simulation list and announce using CreateEntityPdu + * @param newEntity new entity to add + * @return same object to permit progressive setters */ + public SimulationManager addEntity(RecordType newEntity) + { + if (!entityRecordList.contains(newEntity)) + { + // TODO check record type + entityRecordList.add(newEntity); + if (hasDisThreadedNetworkInterface()) + { + CreateEntityPdu createEntityPdu = new CreateEntityPdu(); + // TODO set record parameters + getDisThreadedNetworkInterface().send(createEntityPdu); + } + else + { + System.err.println(TRACE_PREFIX + "addEntity() unable to send CreateEntityPdu since no disThreadedNetworkInterface found"); + // TODO consider queue for unsent entities + } + } + return this; + } + /** + * Remove entity from simulation list, if found + * @param oldEntity old entity to remove + * @return same object to permit progressive setters */ + public SimulationManager removeEntity(RecordType oldEntity) + { + if (!entityRecordList.contains(oldEntity)) + { + // TODO check record type + entityRecordList.remove(oldEntity); + if (hasDisThreadedNetworkInterface()) + { + RemoveEntityPdu removeEntityPdu = new RemoveEntityPdu(); + // TODO set record parameters + getDisThreadedNetworkInterface().send(removeEntityPdu); + } + else + { + System.err.println(TRACE_PREFIX + "removeEntity() unable to send RemoveEntityPdu since no disThreadedNetworkInterface found"); + // TODO consider queue for unsent entities + } + } + return this; + } + /** + * Add host to simulation list, if this is first occurrence + * @param newHost new host to add + * @return same object to permit progressive setters */ + public SimulationManager addHost(String newHost) + { + boolean nameFound = false; + boolean aliasFound = false; + for (RecordType nextRecord : hostRecordList) + { + if ( nextRecord.name.equalsIgnoreCase(newHost.trim())) + nameFound = true; + if (nextRecord.alias.equalsIgnoreCase(newHost.trim())) + aliasFound = true; + if ((nameFound || aliasFound) && !nextRecord.isHostType) + nextRecord.isHostType = true; // make sure + } + if (!nameFound && !aliasFound) + { + RecordType newRecord = new RecordType(hostID, newHost, true); + // TODO set alias to IP number + hostRecordList.add(newRecord); + hostID++; + // no PDU sent + } + return this; + } + /** + * Remove host from simulation list, if found + * @param oldHost old host to remove + * @return same object to permit progressive setters */ + public SimulationManager removeHost(String oldHost) + { + boolean nameFound = false; + boolean aliasFound = false; + for (RecordType nextRecord : hostRecordList) + { + if ( nextRecord.name.equalsIgnoreCase(oldHost.trim())) + nameFound = true; + if (nextRecord.alias.equalsIgnoreCase(oldHost.trim())) + aliasFound = true; + if ((nameFound || aliasFound) && !nextRecord.isHostType) + nextRecord.isHostType = true; // make sure + + if (nameFound || aliasFound) + { + hostRecordList.remove(nextRecord); + // no PDU sent + break; + } + } + return this; + } + + /** Self test to check basic operation, invoked by main() */ + public void selfTest() + { + createDisThreadedNetworkInterface(); + + // TODO + + disThreadedNetworkInterface.close(); // tears down threads and sockets + } + + /** + * Main method for testing. + * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html">Java Tutorials: A Closer Look at the "Hello World!" Application</a> + * @param args [address, port, descriptor] command-line arguments are an array of optional String parameters that are passed from execution environment during invocation + */ + public static void main(String[] args) + { + System.out.println("*** SimulationManager main() self test started..."); + + SimulationManager simulationManager = new SimulationManager("main() self test"); + + simulationManager.setDescriptor("main() self test"); + + simulationManager.selfTest(); + + System.out.println("*** SimulationManager main() self test complete."); + } +} diff --git a/src/edu/nps/moves/dis7/utilities/SimulationManagerLog.txt b/src/edu/nps/moves/dis7/utilities/SimulationManagerLog.txt new file mode 100644 index 0000000000000000000000000000000000000000..b75df43da0992c0188cbd98d508028708bfc63d2 --- /dev/null +++ b/src/edu/nps/moves/dis7/utilities/SimulationManagerLog.txt @@ -0,0 +1,21 @@ +ant -f C:\\x-nps-gitlab\\NetworkedGraphicsMV3500\\examples -Dnb.internal.action.name=run.single -Djavac.includes=OpenDis7Examples/SimulationManager.java -Drun.class=OpenDis7Examples.SimulationManager run-single +init: +Deleting: C:\x-nps-gitlab\NetworkedGraphicsMV3500\examples\build\built-jar.properties +deps-jar: +Updating property file: C:\x-nps-gitlab\NetworkedGraphicsMV3500\examples\build\built-jar.properties +Compiling 1 source file to C:\x-nps-gitlab\NetworkedGraphicsMV3500\examples\build\classes +compile-single: +run-single: +*** SimulationManager main() self test started... +[DisThreadedNetworkInterface] using network interface PANGP Virtual Ethernet Adapter +[DisThreadedNetworkInterface main() self test] datagramSocket.joinGroup address=239.1.2.3 port=3000 isConnected()=false createDatagramSocket() complete. +[DisThreadedNetworkInterface main() self test] createThreads() receiveThread.isAlive()=true +[DisThreadedNetworkInterface main() self test] createThreads() sendingThread.isAlive()=true +*** setKillSentinelAndInterrupts() killed=true sendingThread.isInterrupted()=true receiveThread.isInterrupted()=true +[DisThreadedNetworkInterface main() self test] close(): pdus2send.size()=0 baos.size()=0 dos.size()=0 +[DisThreadedNetworkInterface main() self test] datagramSocket.leaveGroup address=239.1.2.3 port=3000 isClosed()=true close() complete. +*** killThread() status: sendingThread.isAlive()=false sendingThread.isInterrupted()=true +*** killThread() status: receiveThread.isAlive()=false receiveThread.isInterrupted()=true +*** Thread close status: sendingThread.isAlive()=false receiveThread.isAlive()=false +*** SimulationManager main() self test complete. +BUILD SUCCESSFUL (total time: 2 seconds)