Compare commits
9 Commits
6fc2e4d2b9
...
ce9f197a83
Author | SHA1 | Date |
---|---|---|
Barra Ó Catháin | ce9f197a83 | |
Barra Ó Catháin | 060c637c08 | |
Barra Ó Catháin | c282fb20ad | |
Barra Ó Catháin | aa382fefc2 | |
Barra Ó Catháin | fa3df0cc69 | |
Barra Ó Catháin | 4d13547ae6 | |
Barra Ó Catháin | 751a734016 | |
Barra Ó Catháin | 258fd49653 | |
Barra Ó Catháin | 89d520dc15 |
|
@ -108,4 +108,6 @@ SilverMUDServer
|
|||
SilverMUDClient
|
||||
config.h
|
||||
config.h.in
|
||||
stamp-h1
|
||||
stamp-h1
|
||||
|
||||
build/
|
|
@ -1,5 +1,7 @@
|
|||
bin_PROGRAMS = SilverMUDServer SilverMUDClient
|
||||
dist_doc_DATA = README.org
|
||||
schemedir = $(pkgdatadir)/scheme
|
||||
dist_scheme_DATA = lisp
|
||||
SilverMUDServer_CFLAGS = -lgnutls -g $(GUILE_CFLAGS) $(GUILE_LIBS)
|
||||
SilverMUDClient_CFLAGS = -lgnutls -g -lncurses $(GUILE_CFLAGS) $(GUILE_LIBS)
|
||||
|
||||
|
@ -8,7 +10,8 @@ SilverMUDServer_SOURCES = \
|
|||
source/server/player-data.c \
|
||||
source/server/connections.c \
|
||||
source/server/scheme-integration.c \
|
||||
source/server/main.c
|
||||
source/server/output-queue.c \
|
||||
source/server/main.c
|
||||
|
||||
SilverMUDClient_SOURCES = \
|
||||
source/messages.c \
|
||||
|
|
|
@ -2,8 +2,6 @@ AC_INIT([SilverMUD], [0.0.1], [barra@ocathain.ie])
|
|||
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
|
||||
AC_PROG_CC
|
||||
AC_CONFIG_HEADERS([source/config.h])
|
||||
AC_CONFIG_FILES([
|
||||
Makefile
|
||||
])
|
||||
AC_CONFIG_FILES([Makefile])
|
||||
PKG_CHECK_MODULES([GUILE], [guile-3.0])
|
||||
AC_OUTPUT
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
;;;; This file is part of SilverMUD.
|
||||
;;;; structures.scm defines various variables and functions used for interacting with the player's
|
||||
;;;; via chat output from the Scheme enviroment of SilverMUD.
|
||||
(define-module (silvermud messaging))
|
||||
(use-modules (silvermud primitives))
|
||||
|
||||
(define message-everyone (lambda (name content)
|
||||
(push-output-message *global-output-queue* #f *global-player-list*
|
||||
8 name content)))
|
||||
(define system-message (lambda (content)
|
||||
(push-output-message *global-output-queue* #f *global-player-list*
|
||||
0 "" content)))
|
||||
|
||||
(define message-expression (lambda (expression)
|
||||
(system-message (format #f "~a" expression))))
|
||||
|
||||
;; Export everything!
|
||||
(export message-everyone system-message message-expression)
|
|
@ -0,0 +1,67 @@
|
|||
;;;; This file is part of SilverMUD.
|
||||
;;;; structures.scm defines various variables and functions used for interacting with C structures
|
||||
;;;; from the Scheme enviroment of SilverMUD.
|
||||
(define-module (silvermud structures))
|
||||
(use-modules (system foreign)
|
||||
(silvermud primitives))
|
||||
|
||||
;;; struct PlayerList:
|
||||
|
||||
;; The layout of the struct PlayerList:
|
||||
(define *player-list-structure* (list size_t '* '*))
|
||||
|
||||
;; Pretty-format the player list header:
|
||||
(define (player-list-header->string player-list-pointer)
|
||||
"Format a struct PlayerList pointer into a string."
|
||||
(if (not (null-pointer? player-list-pointer))
|
||||
(let ((structure (parse-c-struct player-list-pointer *player-list-structure*)))
|
||||
(format #f
|
||||
"Players in list: ~d.\nHead: ~a. \nTail: ~a.\n"
|
||||
(list-ref structure 0) (list-ref structure 1) (list-ref structure 2)))))
|
||||
|
||||
;; Create a list of strings representing all players in a list:
|
||||
(define (list-players player-list-pointer)
|
||||
"List all players in a given C PlayerList as a list of strings."
|
||||
(if (not (null-pointer? player-list-pointer)) ; Check we're not dereferencing a null pointer.
|
||||
(build-list-players
|
||||
(list-ref (parse-c-struct player-list-pointer *player-list-node-structure*) 2))))
|
||||
|
||||
(define (build-list-players pointer)
|
||||
(if (not (null-pointer? pointer))
|
||||
(let* ((node (parse-c-struct pointer *player-list-node-structure*))
|
||||
(player (parse-c-struct (list-ref node 0) *player-structure*)))
|
||||
(cons (pointer->string (list-ref player 1)) (build-list-players (list-ref node 1))))
|
||||
'()))
|
||||
|
||||
;;; struct PlayerListNode:
|
||||
|
||||
;; Used to interact with struct PlayerListNode:
|
||||
(define *player-list-node-structure* (list '* '* '*))
|
||||
|
||||
;; Pretty-format the player list node:
|
||||
(define (player-list-node->string player-list-node-pointer)
|
||||
"Format a struct PlayerListNode pointer into a string."
|
||||
(if (not (null-pointer? player-list-node-pointer))
|
||||
(let ((structure (parse-c-struct player-list-node-pointer *player-list-node-structure*)))
|
||||
(format #f
|
||||
"Player pointer: ~a.\nNext: ~a. \nPrevious: ~a.\n"
|
||||
(list-ref structure 0) (list-ref structure 1) (list-ref structure 2)))))
|
||||
|
||||
;;; struct Player:
|
||||
|
||||
; Used to interact with struct Player:
|
||||
(define *player-structure* (list '* '*))
|
||||
|
||||
(define (player->string player-info-pointer)
|
||||
"Format a struct Player pointer into a string."
|
||||
(if (not (null-pointer? player-info-pointer))
|
||||
(let ((structure (parse-c-struct player-info-pointer *player-structure*)))
|
||||
(display (null-pointer? (list-ref structure 1)))
|
||||
(format #f
|
||||
"Player Name: ~a\n" (if (null-pointer? (list-ref structure 1))
|
||||
(pointer->bytevector (list-ref structure 1) 64)
|
||||
#f)))))
|
||||
|
||||
;; Export everything!
|
||||
(export *player-list-structure* *player-list-node-structure* *player-structure*
|
||||
player->string player-list-header->string player-list-node->string list-players)
|
|
@ -20,6 +20,7 @@
|
|||
#include <netinet/in.h>
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
#include "output-queue.h"
|
||||
#include "player-data.h"
|
||||
#include "connections.h"
|
||||
#include "../messages.h"
|
||||
|
@ -129,11 +130,6 @@ int main (int argc, char ** argv)
|
|||
// Initialize Scheme:
|
||||
scm_init_guile();
|
||||
|
||||
// Start the REPL server on a UNIX socket:
|
||||
scm_c_eval_string("(begin (use-modules (system repl server))"
|
||||
"(if (file-exists? \"silvermud-repl\") (delete-file \"silvermud-repl\"))"
|
||||
"(spawn-server (make-unix-domain-server-socket #:path \"silvermud-repl\")))");
|
||||
|
||||
// Create a socket to listen for connections on:
|
||||
int masterSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (masterSocket < 0)
|
||||
|
@ -199,6 +195,24 @@ int main (int argc, char ** argv)
|
|||
|
||||
// Create some structures needed to store global state:
|
||||
struct PlayerList * globalPlayerList = createPlayerList();
|
||||
struct OutputQueue * globalOutputQueue = createOutputQueue();
|
||||
|
||||
// Define a module for use in the REPL containing our needed primitives:
|
||||
SchemeModulePointers schemePointers;
|
||||
schemePointers.globalPlayerList = globalPlayerList;
|
||||
schemePointers.globalOutputQueue = globalOutputQueue;
|
||||
|
||||
scm_c_define_module("silvermud primitives", initialize_silvermud_primitives, &schemePointers);
|
||||
scm_c_use_module("silvermud primitives");
|
||||
|
||||
// Start the REPL server on a UNIX socket:
|
||||
scm_c_eval_string("(begin (use-modules (system repl server))"
|
||||
"(if (file-exists? \"silvermud-repl\") (delete-file \"silvermud-repl\"))"
|
||||
"(spawn-server (make-unix-domain-server-socket #:path \"silvermud-repl\")))");
|
||||
|
||||
// Start an output thread:
|
||||
pthread_t outputThread;
|
||||
pthread_create(&outputThread, NULL, outputThreadHandler, (void *)globalOutputQueue);
|
||||
|
||||
// Start a REPL thread:
|
||||
//pthread_t schemeREPLThread;
|
||||
|
@ -311,23 +325,10 @@ int main (int argc, char ** argv)
|
|||
continue;
|
||||
}
|
||||
// ONLY FOR DEMO
|
||||
|
||||
struct ServerToClientMessage outputMessage;
|
||||
|
||||
// Copy the message to the output format:
|
||||
outputMessage.type = LOCAL_CHAT;
|
||||
|
||||
strncpy(outputMessage.name, connection->player->name, 64);
|
||||
strncpy(outputMessage.content, message.content, MESSAGE_CONTENT_LENGTH);
|
||||
|
||||
// Echo the message into all other clients: (Temporary)
|
||||
struct ClientConnectionNode * currentClient = clientConnections.head;
|
||||
while (currentClient != NULL)
|
||||
{
|
||||
gnutls_record_send(*currentClient->connection->tlsSession, &outputMessage,
|
||||
sizeof(struct ServerToClientMessage));
|
||||
currentClient = currentClient->next;
|
||||
}
|
||||
pushOutputMessage(globalOutputQueue, false, globalPlayerList, LOCAL_CHAT,
|
||||
connection->player->name, message.content,
|
||||
MESSAGE_NAME_LENGTH, MESSAGE_CONTENT_LENGTH);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -339,7 +340,7 @@ int main (int argc, char ** argv)
|
|||
close(events[index].data.fd);
|
||||
removeConnectionByFileDescriptor(&clientConnections, events[index].data.fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
// =========================================
|
||||
// | SilverMUD Server - output-queue.c |
|
||||
// | Copyright (C) 2023, Barra Ó Catháin |
|
||||
// | See end of file for copyright notice. |
|
||||
// =========================================
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include "player-data.h"
|
||||
#include "output-queue.h"
|
||||
|
||||
// A thread handler for constantly outputting messages from an output queue:
|
||||
void * outputThreadHandler(void * outputQueue)
|
||||
{
|
||||
struct OutputQueue * queue = (struct OutputQueue *)outputQueue;
|
||||
struct OutputMessage * currentMessage = NULL;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (queue->count == 0)
|
||||
{
|
||||
pthread_cond_wait(&queue->updated, &queue->waitMutex);
|
||||
}
|
||||
|
||||
currentMessage = popOutputMessage(queue);
|
||||
if (currentMessage != NULL)
|
||||
{
|
||||
struct PlayerListNode * currentPlayerNode = currentMessage->recepients->head;
|
||||
|
||||
while (currentPlayerNode != NULL)
|
||||
{
|
||||
gnutls_record_send(*currentPlayerNode->player->connection->tlsSession,
|
||||
currentMessage->message, sizeof(struct ServerToClientMessage));
|
||||
currentPlayerNode = currentPlayerNode->next;
|
||||
}
|
||||
|
||||
if (currentMessage->deallocatePlayerList == true)
|
||||
{
|
||||
deallocatePlayerList(¤tMessage->recepients);
|
||||
}
|
||||
|
||||
deallocateOutputMessage(¤tMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OutputQueue * const createOutputQueue()
|
||||
{
|
||||
// Allocate a new queue:
|
||||
struct OutputQueue * const newQueue = calloc(1, sizeof(struct OutputQueue));
|
||||
|
||||
// Initialize it:
|
||||
pthread_mutex_init(&newQueue->mutex, NULL);
|
||||
pthread_cond_init(&newQueue->updated, NULL);
|
||||
newQueue->count = 0;
|
||||
newQueue->front = NULL;
|
||||
newQueue->back = NULL;
|
||||
|
||||
// Return the new queue:
|
||||
return newQueue;
|
||||
}
|
||||
|
||||
size_t pushOutputMessage(struct OutputQueue * const queue,
|
||||
const bool deallocatePlayerList,
|
||||
struct PlayerList * const recepients,
|
||||
const enum MessageTypes type,
|
||||
const char const * name, const char const * content,
|
||||
const size_t nameLength, const size_t contentLength)
|
||||
{
|
||||
// Allocate the appropriate memory for the queued message:
|
||||
struct OutputMessage * newMessage = calloc(1, sizeof(struct OutputMessage));
|
||||
newMessage->message = calloc(1, sizeof(struct ServerToClientMessage));
|
||||
|
||||
// Copy in the appropriate values to the ServerToClientMessage:
|
||||
newMessage->message->type = type;
|
||||
|
||||
strncpy(newMessage->message->name, name, (nameLength < MESSAGE_NAME_LENGTH) ?
|
||||
nameLength : MESSAGE_NAME_LENGTH);
|
||||
newMessage->message->name[MESSAGE_NAME_LENGTH - 1] = '\0';
|
||||
|
||||
strncpy(newMessage->message->content, content, (contentLength < MESSAGE_CONTENT_LENGTH) ?
|
||||
contentLength : MESSAGE_CONTENT_LENGTH);
|
||||
newMessage->message->content[MESSAGE_CONTENT_LENGTH - 1] = '\0';
|
||||
|
||||
// Copy in the appropriate values to the OutputMessage:
|
||||
newMessage->deallocatePlayerList = deallocatePlayerList;
|
||||
newMessage->recepients = recepients;
|
||||
|
||||
// Entering critical section - Lock the queue:
|
||||
pthread_mutex_lock(&queue->mutex);
|
||||
|
||||
// Add it to the queue:
|
||||
if (queue->back != NULL)
|
||||
{
|
||||
queue->back->next = newMessage;
|
||||
queue->back = newMessage;
|
||||
}
|
||||
|
||||
if (queue->front == NULL)
|
||||
{
|
||||
queue->front = newMessage;
|
||||
queue->back = newMessage;
|
||||
}
|
||||
|
||||
queue->count++;
|
||||
|
||||
// Leaving critical section - Unlock the queue:
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
|
||||
pthread_cond_signal(&queue->updated);
|
||||
|
||||
return queue->count;
|
||||
}
|
||||
|
||||
struct OutputMessage * popOutputMessage(struct OutputQueue * queue)
|
||||
{
|
||||
if (queue->count == 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Entering the critical section - Lock the queue:
|
||||
pthread_mutex_lock(&queue->mutex);
|
||||
|
||||
struct OutputMessage * message = queue->front;
|
||||
queue->count--;
|
||||
|
||||
if(queue->count == 0)
|
||||
{
|
||||
queue->front = NULL;
|
||||
queue->back = NULL;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
queue->front = queue->front->next;
|
||||
}
|
||||
|
||||
// Leaving the critical section - Unlock the queue:
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
void deallocateOutputMessage(struct OutputMessage ** message)
|
||||
{
|
||||
// Free and set the pointer to NULL:
|
||||
free(*message);
|
||||
message = NULL;
|
||||
}
|
||||
|
||||
// ====================================================
|
||||
// | End of output-queue.c, copyright notice follows. |
|
||||
// ====================================================
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
// =========================================
|
||||
// | SilverMUD Server - output-queue.h |
|
||||
// | Copyright (C) 2023, Barra Ó Catháin |
|
||||
// | See end of file for copyright notice. |
|
||||
// =========================================
|
||||
#ifndef OUTPUT_QUEUE_H
|
||||
#define OUTPUT_QUEUE_H
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "../messages.h"
|
||||
|
||||
struct OutputMessage
|
||||
{
|
||||
// Allows for easy reuse of existing player lists, such as the global list
|
||||
// or an area's playerlist:
|
||||
bool deallocatePlayerList;
|
||||
|
||||
struct OutputMessage * next;
|
||||
struct PlayerList * recepients;
|
||||
struct ServerToClientMessage * message;
|
||||
};
|
||||
|
||||
struct OutputQueue
|
||||
{
|
||||
pthread_mutex_t mutex;
|
||||
pthread_mutex_t waitMutex;
|
||||
pthread_cond_t updated;
|
||||
size_t count;
|
||||
struct OutputMessage * front, * back;
|
||||
};
|
||||
|
||||
void * outputThreadHandler(void * outputQueue);
|
||||
|
||||
struct OutputQueue * const createOutputQueue();
|
||||
|
||||
size_t pushOutputMessage(struct OutputQueue * const queue,
|
||||
const bool deallocatePlayerList,
|
||||
struct PlayerList * const recepients,
|
||||
const enum MessageTypes type,
|
||||
const char const * name, const char const * content,
|
||||
const size_t nameLength, const size_t contentLength);
|
||||
|
||||
struct OutputMessage * popOutputMessage(struct OutputQueue * queue);
|
||||
|
||||
void deallocateOutputMessage(struct OutputMessage ** message);
|
||||
|
||||
#endif
|
||||
// ====================================================
|
||||
// | End of output-queue.h, copyright notice follows. |
|
||||
// ====================================================
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
|
@ -31,6 +31,7 @@ struct Player * createNewPlayer(struct ClientConnection * connection)
|
|||
{
|
||||
struct Player * newPlayer = calloc(1, sizeof(struct Player));
|
||||
newPlayer->connection = connection;
|
||||
newPlayer->name = calloc(PLAYER_NAME_LENGTH, sizeof(char));
|
||||
|
||||
return newPlayer;
|
||||
}
|
||||
|
@ -38,6 +39,7 @@ struct Player * createNewPlayer(struct ClientConnection * connection)
|
|||
// Deallocates a player:
|
||||
void deallocatePlayer(struct Player ** player)
|
||||
{
|
||||
free((*player)->name);
|
||||
free(*player);
|
||||
*player = NULL;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// =========================================
|
||||
#ifndef PLAYER_DATA_H
|
||||
#define PLAYER_DATA_H
|
||||
#define PLAYER_NAME_LENGTH 64
|
||||
#include <stdbool.h>
|
||||
#include "connections.h"
|
||||
|
||||
|
@ -14,7 +15,7 @@
|
|||
struct Player
|
||||
{
|
||||
struct ClientConnection * connection;
|
||||
char name[64];
|
||||
char * name;
|
||||
};
|
||||
|
||||
// Functions:
|
||||
|
|
|
@ -3,9 +3,47 @@
|
|||
// | Copyright (C) 2023, Barra Ó Catháin |
|
||||
// | See end of file for copyright notice. |
|
||||
// ===========================================
|
||||
#include <stdbool.h>
|
||||
#include <libguile.h>
|
||||
|
||||
SCM scheme_get_player_by_name(SCM name);
|
||||
#include "../messages.h"
|
||||
#include "output-queue.h"
|
||||
#include "scheme-integration.h"
|
||||
|
||||
void initialize_silvermud_primitives (void * gameState)
|
||||
{
|
||||
SchemeModulePointers * pointers = (SchemeModulePointers *)gameState;
|
||||
scm_c_define_gsubr("push-output-message", 6, 0, 0, &push_output_message);
|
||||
scm_c_define("*global-player-list*", scm_from_pointer(pointers->globalPlayerList, NULL));
|
||||
scm_c_define("*global-output-queue*", scm_from_pointer(pointers->globalOutputQueue, NULL));
|
||||
scm_c_export("push-output-message", "*global-player-list*", "*global-output-queue*", NULL);
|
||||
}
|
||||
|
||||
//SCM scheme_get_player_by_name(SCM name, SCM queue)
|
||||
|
||||
SCM push_output_message(SCM queue, SCM deallocate_list, SCM recepients, SCM type, SCM name, SCM content)
|
||||
{
|
||||
// Convert our scheme values into appropriate data types:
|
||||
struct OutputQueue * queue_c = scm_to_pointer(queue);
|
||||
bool deallocate_list_c = scm_to_bool(deallocate_list);
|
||||
struct PlayerList * recepients_c = scm_to_pointer(recepients);
|
||||
enum MessageTypes type_c = scm_to_int(type);
|
||||
|
||||
// Turn the Scheme strings into C strings:
|
||||
size_t nameLength, contentLength;
|
||||
char * name_c = scm_to_locale_stringn(name, &nameLength);
|
||||
char * content_c = scm_to_locale_stringn(content, &contentLength);
|
||||
|
||||
// Call the C function:
|
||||
pushOutputMessage(queue_c, deallocate_list_c, recepients_c, type_c, name_c, content_c,
|
||||
nameLength, contentLength);
|
||||
|
||||
// Free the created C strings:
|
||||
free(name_c);
|
||||
free(content_c);
|
||||
|
||||
return SCM_BOOL_T;
|
||||
}
|
||||
|
||||
// ==========================================================
|
||||
// | End of scheme-integration.c, copyright notice follows. |
|
||||
|
|
|
@ -6,8 +6,18 @@
|
|||
#ifndef SCHEME_INTEGRATION_H
|
||||
#define SCHEME_INTEGRATION_H
|
||||
|
||||
typedef struct SchemeModulePointers
|
||||
{
|
||||
struct PlayerList * globalPlayerList;
|
||||
struct OutputQueue * globalOutputQueue;
|
||||
} SchemeModulePointers;
|
||||
|
||||
void initialize_silvermud_primitives (void * gameState);
|
||||
|
||||
SCM scheme_get_player_by_name(SCM name);
|
||||
|
||||
SCM push_output_message(SCM queue, SCM deallocate_list, SCM recepients, SCM type, SCM name, SCM content);
|
||||
|
||||
#endif
|
||||
// ==========================================================
|
||||
// | End of scheme-integration.h, copyright notice follows. |
|
||||
|
|
Loading…
Reference in New Issue