/*******************************************************************************
* Copyright 2015 Klaus Pfeiffer <klaus@allpiper.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.jfastnet.messages;
import com.jfastnet.Config;
import com.jfastnet.State;
import com.jfastnet.messages.features.MessageFeatures;
import com.jfastnet.messages.features.TimestampFeature;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.net.InetSocketAddress;
/** The base class for all messages. Subclass this class for your own messages.
* @param <E> context object for the processing of the message
* @author Klaus Pfeiffer - klaus@allpiper.com */
@Slf4j
@Getter
@EqualsAndHashCode
@Accessors(chain = true)
public abstract class Message<E> implements Serializable, Comparable<Message> {
/** */
private static final long serialVersionUID = 1L;
public static final MessageFeatures DEFAULT_MESSAGE_FEATURES = new MessageFeatures.Immutable();
/** Unique message id. */
@Getter
long msgId;
/** Sender id of message. <b>Attention!</b> Don't use this in responses,
* as it will always be the host's id! */
@Setter
private int senderId;
/** Received id is only used during sending. */
@Setter @Getter
private transient int receiverId;
/** A message that is getting resent, because of unsuccessful transmission.
* These messages may not be stopped from sending, when we are over threshold
* because then the server could stall out if enough messages get lost and
* it is waiting for acknowledge messages. */
@Setter @Getter
private transient boolean resendMessage;
/** Address from receiving or to sending socket. */
public transient InetSocketAddress socketAddressSender;
/** Address from recipient. */
public transient InetSocketAddress socketAddressRecipient;
/** Serialized payload of message. The data that actually gets transmitted. */
public transient Object payload;
/** Config gets set upon sending / receiving of message. */
@Setter @Getter
private transient Config config;
/** State gets set upon sending / receiving of message. */
@Setter @Getter
private transient State state;
public Message() {}
public Message copyAttributesFrom(Message message) {
config = message.config;
state = message.state;
socketAddressSender = message.socketAddressSender;
senderId = message.senderId;
return this;
}
/** Additional features can be specified for every message.
* E.g. when a message should contain a timestamp or when the message
* has to be secured with a hash value. */
public MessageFeatures getFeatures() {
return DEFAULT_MESSAGE_FEATURES;
}
public void resolve(Config config, State state) {
this.config = config;
this.state = state;
this.senderId = config.senderId;
getFeatures().resolveConfig(config);
}
/** Clear id. */
public void clearId() {
this.msgId = 0;
}
/** Resolve id via id provider. */
public void resolveId() {
this.msgId = state.idProvider.createIdFor(this);
log.trace(" * Resolved ID {} for {}", msgId, this);
}
/** Method called on processing of message.
* @param context context object */
public void process(E context) {}
@Override
public int compareTo(final Message o) {
if (config != null) {
return state.idProvider.compare(this, o);
}
// compare by player id
int compare = Integer.compare(getReceiverId(), o.getReceiverId());
if (compare != 0) return compare;
// compare by reliable mode
compare = Integer.compare(getReliableMode().ordinal(), o.getReliableMode().ordinal());
if (compare != 0) return compare;
// second by id
compare = Long.compare(msgId, o.msgId);
if (compare != 0) return compare;
return compare;
}
/** Override if you need something done before sending. */
public void prepareToSend() {}
/** Override if you need something done before receiving. */
public Message<E> beforeReceive() { return this; }
/** Specify the reliable mode for this message. */
public ReliableMode getReliableMode() { return ReliableMode.SEQUENCE_NUMBER; }
/** If this message is sent with the ACK reliable mode and is then
* acknowledged from the other side, this callback is called. */
public void ackCallback() {}
/** If this message can be stacked. Only use in conjunction with SEQUENCE_NUMBER. */
public boolean stackable() { return false; }
/** If this message should be broadcasted by the server upon receipt. */
public boolean broadcast() { return false; }
/** Whether message should be sent back to sender when broadcasting. */
public boolean sendBroadcastBackToSender() { return true; }
/** You can specify a unique key for this message. If the same key is
* received another time, the message will be discarded. */
public Object getDiscardableKey() { return null; }
/** If timeout is greater than zero the message will be discarded if
* received too late.
* @return timeout in ms */
public int getTimeOut() { return 0; }
/** If used, must be handled by the receiver manually. */
public int executionPriority() { return 0; }
/** @return true if message should be discarded. */
public boolean discard() {
if (getTimeOut() > 0 && config.timeProvider.get() > getTimestamp() + getTimeOut()) {
log.trace("Message {} discarded. TimeProvider: {}, TimeStamp+TimeOut: {}", new Object[]{this, config.timeProvider.get(), getTimestamp() + getTimeOut()});
return true;
} else {
return false;
}
}
public String toString() {
return getClass().getName() + "(msgId=" + this.msgId + ", reliableMode=" + getReliableMode() + ", senderId=" + this.senderId + ", receiverId=" + this.receiverId + ")";
}
public long getTimestamp() {
TimestampFeature timestampFeature = getFeatures().get(TimestampFeature.class);
if (timestampFeature != null) {
return timestampFeature.timestamp;
}
return 0L;
}
public int payloadLength() {
if (payload instanceof byte[]) {
byte[] payload = (byte[]) this.payload;
return payload.length;
} else {
return -1;
}
}
public enum ReliableMode {
/** Message doesn't have to be transmitted reliably. */
UNRELIABLE,
/** Receiving side sends acknowledge packet to confirm receit. */
ACK_PACKET,
/** Receiving side checks consecutively numbered messages. */
SEQUENCE_NUMBER,
}
}