mame/src/emu/inputseq.c
Aaron Giles ad4910a8a8 Bulk change alert.
This update changes the way we handle memory allocation. Rather
than allocating in terms of bytes, allocations are now done in
terms of objects. This is done via new set of macros that replace
the malloc_or_die() macro:

  alloc_or_die(t) - allocate memory for an object of type 't'
  alloc_array_or_die(t,c) - allocate memory for an array of 'c' objects of type 't'
  alloc_clear_or_die(t) - same as alloc_or_die but memset's the memory to 0
  alloc_array_clear_or_die(t,c) - same as alloc_array_or_die but memset's the memory to 0

All original callers of malloc_or_die have been updated to call these
new macros. If you just need an array of bytes, you can use
alloc_array_or_die(UINT8, numbytes).

Made a similar change to the auto_* allocation macros. In addition,
added 'machine' as a required parameter to the auto-allocation macros,
as the resource pools will eventually be owned by the machine object.
The new macros are:

  auto_alloc(m,t) - allocate memory for an object of type 't'
  auto_alloc_array(m,t,c) - allocate memory for an array of 'c' objects of type 't'
  auto_alloc_clear(m,t) - allocate and memset
  auto_alloc_array_clear(m,t,c) - allocate and memset

All original calls or auto_malloc have been updated to use the new
macros. In addition, auto_realloc(), auto_strdup(), auto_astring_alloc(),
and auto_bitmap_alloc() have been updated to take a machine parameter.

Changed validity check allocations to not rely on auto_alloc* anymore
because they are not done in the context of a machine.

One final change that is included is the removal of SMH_BANKn macros.
Just use SMH_BANK(n) instead, which is what the previous macros mapped
to anyhow.
2009-04-26 23:54:37 +00:00

652 lines
18 KiB
C

/***************************************************************************
inputseq.c
Input sequence abstractions.
Copyright Nicola Salmoria and the MAME Team.
Visit http://mamedev.org for licensing and usage restrictions.
***************************************************************************/
#include "inputseq.h"
#include "restrack.h"
#include <ctype.h>
/***************************************************************************
CONSTANTS
***************************************************************************/
/* max time between key presses */
#define RECORD_TIME (osd_ticks_per_second() * 2 / 3)
/***************************************************************************
GLOBAL VARIABLES
***************************************************************************/
/* information about the current sequence being recorded */
static input_seq record_seq;
static osd_ticks_t record_last;
static input_item_class record_class;
/***************************************************************************
FUNCTION PROTOTYPES
***************************************************************************/
/* internal functions */
static int input_seq_is_valid(const input_seq *seq);
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
input_seq_length - return the length of the
sequence
-------------------------------------------------*/
INLINE int input_seq_length(const input_seq *seq)
{
int seqnum;
/* find the end token; error if none found */
for (seqnum = 0; seqnum < ARRAY_LENGTH(seq->code); seqnum++)
if (seq->code[seqnum] == SEQCODE_END)
return seqnum;
return ARRAY_LENGTH(seq->code);
}
/*-------------------------------------------------
input_seq_append - append a code to the end
of an input sequence
-------------------------------------------------*/
INLINE int input_seq_append(input_seq *seq, input_code code)
{
int length = input_seq_length(seq);
/* if not enough room, return FALSE */
if (length >= ARRAY_LENGTH(seq->code) - 1)
return FALSE;
/* otherwise, append the code and add a new end */
seq->code[length++] = code;
seq->code[length] = SEQCODE_END;
return TRUE;
}
/*-------------------------------------------------
input_seq_get_last - return the last code
in a sequence
-------------------------------------------------*/
INLINE input_code input_seq_get_last(const input_seq *seq)
{
int length = input_seq_length(seq);
return (length == 0) ? SEQCODE_END : seq->code[length - 1];
}
/*-------------------------------------------------
input_seq_get_last_but_one - return the
last_but_one code in a sequence
-------------------------------------------------*/
INLINE input_code input_seq_get_last_but_one(const input_seq *seq)
{
int length = input_seq_length(seq);
return (length < 2) ? SEQCODE_END : seq->code[length - 2];
}
/*-------------------------------------------------
input_seq_backspace - "backspace" over the
last entry in a sequence
-------------------------------------------------*/
INLINE void input_seq_backspace(input_seq *seq)
{
int length = input_seq_length(seq);
/* if we have at least one entry, remove it */
if (length > 0)
seq->code[length - 1] = SEQCODE_END;
}
/***************************************************************************
STATE QUERIES
***************************************************************************/
/*-------------------------------------------------
input_seq_pressed - return true if the given
sequence of switch inputs is "pressed"
-------------------------------------------------*/
int input_seq_pressed(const input_seq *seq)
{
int result = FALSE;
int invert = FALSE;
int first = TRUE;
int codenum;
/* iterate over all of the codes */
for (codenum = 0; codenum < ARRAY_LENGTH(seq->code); codenum++)
{
input_code code = seq->code[codenum];
/* handle NOT */
if (code == SEQCODE_NOT)
invert = TRUE;
/* handle OR and END */
else if (code == SEQCODE_OR || code == SEQCODE_END)
{
/* if we have a positive result from the previous set, we're done */
if (result || code == SEQCODE_END)
break;
/* otherwise, reset our state */
result = FALSE;
invert = FALSE;
first = TRUE;
}
/* handle everything else as a series of ANDs */
else
{
/* if this is the first in the sequence, result is set equal */
if (first)
result = input_code_pressed(code) ^ invert;
/* further values are ANDed */
else if (result)
result &= input_code_pressed(code) ^ invert;
/* no longer first, and clear the invert flag */
first = invert = FALSE;
}
}
/* return the result if we queried at least one switch */
return result;
}
/*-------------------------------------------------
input_seq_axis_value - return the value of an
axis defined in an input sequence
-------------------------------------------------*/
INT32 input_seq_axis_value(const input_seq *seq, input_item_class *itemclass_ptr)
{
input_item_class itemclasszero = ITEM_CLASS_ABSOLUTE;
input_item_class itemclass = ITEM_CLASS_INVALID;
int result = 0;
int invert = FALSE;
int enable = TRUE;
int codenum;
/* iterate over all of the codes */
for (codenum = 0; codenum < ARRAY_LENGTH(seq->code); codenum++)
{
input_code code = seq->code[codenum];
/* handle NOT */
if (code == SEQCODE_NOT)
invert = TRUE;
/* handle OR and END */
else if (code == SEQCODE_OR || code == SEQCODE_END)
{
/* if we have a positive result from the previous set, we're done */
if (itemclass != ITEM_CLASS_INVALID || code == SEQCODE_END)
break;
/* otherwise, reset our state */
result = 0;
invert = FALSE;
enable = TRUE;
}
/* handle everything else only if we're still enabled */
else if (enable)
{
/* switch codes serve as enables */
if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_SWITCH)
{
/* AND against previous digital codes */
if (enable)
enable &= input_code_pressed(code) ^ invert;
}
/* non-switch codes are analog values */
else
{
INT32 value = input_code_value(code);
/* if we got a 0 value, don't do anything except remember the first type */
if (value == 0)
{
if (itemclasszero == ITEM_CLASS_INVALID)
itemclasszero = INPUT_CODE_ITEMCLASS(code);
}
/* non-zero absolute values stick */
else if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_ABSOLUTE)
{
itemclass = ITEM_CLASS_ABSOLUTE;
result = value;
}
/* non-zero relative values accumulate */
else if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_RELATIVE)
{
itemclass = ITEM_CLASS_RELATIVE;
result += value;
}
}
/* clear the invert flag */
invert = FALSE;
}
}
/* if the caller wants to know the type, provide it */
if (itemclass_ptr != NULL)
*itemclass_ptr = (result == 0) ? itemclasszero : itemclass;
return result;
}
/***************************************************************************
SEQUENCE POLLING
***************************************************************************/
/*-------------------------------------------------
input_seq_poll_start - begin polling for a
new sequence of the given itemclass
-------------------------------------------------*/
void input_seq_poll_start(input_item_class itemclass, const input_seq *startseq)
{
input_code dummycode;
assert(itemclass == ITEM_CLASS_SWITCH || itemclass == ITEM_CLASS_ABSOLUTE || itemclass == ITEM_CLASS_RELATIVE);
/* reset the recording count and the clock */
record_last = 0;
record_class = itemclass;
/* grab the starting sequence to append to */
if (startseq != NULL)
record_seq = *startseq;
else
input_seq_set_0(&record_seq);
/* append an OR if this is not a NULL sequence */
if (input_seq_length(&record_seq) > 0)
input_seq_append(&record_seq, SEQCODE_OR);
/* flush out any goobers */
dummycode = (record_class == ITEM_CLASS_SWITCH) ? input_code_poll_switches(TRUE) : input_code_poll_axes(TRUE);
while (dummycode != INPUT_CODE_INVALID)
dummycode = (record_class == ITEM_CLASS_SWITCH) ? input_code_poll_switches(FALSE) : input_code_poll_axes(FALSE);
}
/*-------------------------------------------------
input_seq_poll - continue polling
-------------------------------------------------*/
int input_seq_poll(input_seq *finalseq)
{
input_code lastcode = input_seq_get_last(&record_seq);
int has_or = FALSE;
input_code newcode;
/* switch case: see if we have a new code to process */
if (record_class == ITEM_CLASS_SWITCH)
{
newcode = input_code_poll_switches(FALSE);
if (newcode != INPUT_CODE_INVALID)
{
/* if code is duplicate, toggle the NOT state on the code */
if (input_seq_length(&record_seq) > 0 && newcode == lastcode)
{
/* back up over the code */
input_seq_backspace(&record_seq);
/* if there was a NOT preceding it, just delete it, otherwise append one */
if (input_seq_get_last(&record_seq) == SEQCODE_NOT)
input_seq_backspace(&record_seq);
else
input_seq_append(&record_seq, SEQCODE_NOT);
}
}
}
/* absolute/relative case: see if we have an analog change of sufficient amount */
else
{
if (lastcode == SEQCODE_OR)
{
lastcode = input_seq_get_last_but_one(&record_seq);
has_or = TRUE;
}
newcode = input_code_poll_axes(FALSE);
/* if the last code doesn't match absolute/relative of this code, ignore the new one */
if ((INPUT_CODE_ITEMCLASS(lastcode) == ITEM_CLASS_ABSOLUTE && INPUT_CODE_ITEMCLASS(newcode) != ITEM_CLASS_ABSOLUTE) ||
(INPUT_CODE_ITEMCLASS(lastcode) == ITEM_CLASS_RELATIVE && INPUT_CODE_ITEMCLASS(newcode) != ITEM_CLASS_RELATIVE))
newcode = INPUT_CODE_INVALID;
if (newcode != INPUT_CODE_INVALID)
{
/* if code is duplicate and an absolute control, toggle to half axis */
if (input_seq_length(&record_seq) > 0 && INPUT_CODE_ITEMCLASS(newcode) == ITEM_CLASS_ABSOLUTE)
{
if (newcode == INPUT_CODE_SET_MODIFIER(lastcode, ITEM_MODIFIER_NONE))
{
/* increment the modifier, wrapping back to none */
input_item_modifier oldmod = INPUT_CODE_MODIFIER(lastcode);
input_item_modifier newmod = (oldmod < ITEM_MODIFIER_NEG) ? (input_item_modifier)(oldmod + 1) : ITEM_MODIFIER_NONE;
newcode = INPUT_CODE_SET_MODIFIER(newcode, newmod);
/* back up over the previous code so we can re-append */
if (has_or)
input_seq_backspace(&record_seq);
input_seq_backspace(&record_seq);
}
}
}
}
/* if we got a new code to append it, append it and reset the timer */
if (newcode != INPUT_CODE_INVALID)
{
input_seq_append(&record_seq, newcode);
record_last = osd_ticks();
}
/* if we're recorded at least one item and the RECORD_TIME has passed, we're done */
if (record_last != 0 && (osd_ticks() > record_last + RECORD_TIME))
{
/* if the final result is invalid, reset to nothing */
if (!input_seq_is_valid(&record_seq))
input_seq_set_0(&record_seq);
/* return TRUE to indicate that we are finished */
*finalseq = record_seq;
return TRUE;
}
/* return FALSE to indicate we are still polling */
return FALSE;
}
/***************************************************************************
STRINGS AND TOKENIZATION
***************************************************************************/
/*-------------------------------------------------
input_seq_name - generate the friendly name
of a sequence
-------------------------------------------------*/
astring *input_seq_name(astring *string, const input_seq *seq)
{
astring *codestr = astring_alloc();
int codenum, copycodenum;
input_seq seqcopy;
/* walk the sequence first, removing any pieces that are invalid */
for (codenum = copycodenum = 0; codenum < ARRAY_LENGTH(seq->code) && seq->code[codenum] != SEQCODE_END; codenum++)
{
input_code code = seq->code[codenum];
/* if this is a code item which is not valid, don't copy it and remove any preceding ORs/NOTs */
if (!INPUT_CODE_IS_INTERNAL(code) && astring_len(input_code_name(codestr, code)) == 0)
{
while (copycodenum > 0 && INPUT_CODE_IS_INTERNAL(seqcopy.code[copycodenum - 1]))
copycodenum--;
}
else if (copycodenum > 0 || !INPUT_CODE_IS_INTERNAL(code))
seqcopy.code[copycodenum++] = code;
}
seqcopy.code[copycodenum] = SEQCODE_END;
/* special case: empty */
if (copycodenum == 0)
{
astring_free(codestr);
return astring_cpyc(string, (seq->code[0] == SEQCODE_END) ? "None" : "n/a");
}
/* start with an empty buffer */
astring_reset(string);
/* loop until we hit the end */
for (codenum = 0; codenum < ARRAY_LENGTH(seqcopy.code) && seqcopy.code[codenum] != SEQCODE_END; codenum++)
{
input_code code = seqcopy.code[codenum];
/* append a space if not the first code */
if (codenum != 0)
astring_catc(string, " ");
/* handle OR/NOT codes here */
if (code == SEQCODE_OR)
astring_catc(string, "or");
else if (code == SEQCODE_NOT)
astring_catc(string, "not");
/* otherwise, assume it is an input code and ask the input system to generate it */
else
astring_cat(string, input_code_name(codestr, code));
}
astring_free(codestr);
return string;
}
/*-------------------------------------------------
input_seq_to_tokens - generate the tokenized
form of a sequence
-------------------------------------------------*/
astring *input_seq_to_tokens(astring *string, const input_seq *seq)
{
astring *codestr = astring_alloc();
int codenum;
/* start with an empty buffer */
astring_reset(string);
/* loop until we hit the end */
for (codenum = 0; codenum < ARRAY_LENGTH(seq->code) && seq->code[codenum] != SEQCODE_END; codenum++)
{
input_code code = seq->code[codenum];
/* append a space if not the first code */
if (codenum != 0)
astring_catc(string, " ");
/* handle OR/NOT codes here */
if (code == SEQCODE_OR)
astring_catc(string, "OR");
else if (code == SEQCODE_NOT)
astring_catc(string, "NOT");
else if (code == SEQCODE_DEFAULT)
astring_catc(string, "DEFAULT");
/* otherwise, assume it is an input code and ask the input system to generate it */
else
astring_cat(string, input_code_to_token(codestr, code));
}
astring_free(codestr);
return string;
}
/*-------------------------------------------------
input_seq_from_tokens - generate the tokenized
form of a sequence
-------------------------------------------------*/
int input_seq_from_tokens(const char *string, input_seq *seq)
{
char *strcopy = alloc_array_or_die(char, strlen(string) + 1);
char *str = strcopy;
int result = FALSE;
/* start with a blank sequence */
input_seq_set_0(seq);
/* loop until we're done */
strcpy(strcopy, string);
while (1)
{
input_code code;
char origspace;
char *strtemp;
/* trim any leading spaces */
while (*str != 0 && isspace(*str))
str++;
/* bail if we're done */
if (*str == 0)
{
result = TRUE;
break;
}
/* find the end of the token and make it upper-case along the way */
for (strtemp = str; *strtemp != 0 && !isspace(*strtemp); strtemp++)
*strtemp = toupper(*strtemp);
origspace = *strtemp;
*strtemp = 0;
/* look for common stuff */
if (strcmp(str, "OR") == 0)
code = SEQCODE_OR;
else if (strcmp(str, "NOT") == 0)
code = SEQCODE_NOT;
else if (strcmp(str, "DEFAULT") == 0)
code = SEQCODE_DEFAULT;
else
code = input_code_from_token(str);
/* translate and add to the sequence */
input_seq_append(seq, code);
/* advance */
if (origspace == 0)
{
result = TRUE;
break;
}
str = strtemp + 1;
}
free(strcopy);
return result;
}
/***************************************************************************
INTERNAL FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
input_seq_is_valid - return TRUE if a given
sequence is valid
-------------------------------------------------*/
static int input_seq_is_valid(const input_seq *seq)
{
input_item_class lastclass = ITEM_CLASS_INVALID;
input_code lastcode = INPUT_CODE_INVALID;
int positive_code_count = 0;
int seqnum;
/* scan the sequence for valid codes */
for (seqnum = 0; seqnum < ARRAY_LENGTH(seq->code); seqnum++)
{
input_code code = seq->code[seqnum];
/* invalid codes are invalid */
if (code == INPUT_CODE_INVALID)
return FALSE;
/* if we hit an OR or the end, validate the previous chunk */
if (code == SEQCODE_OR || code == SEQCODE_END)
{
/* must be at least one positive code */
if (positive_code_count == 0)
return FALSE;
/* last code must not have been an internal code */
if (INPUT_CODE_IS_INTERNAL(lastcode))
return FALSE;
/* if this is the end, we're ok */
if (code == SEQCODE_END)
return TRUE;
/* reset the state for the next chunk */
positive_code_count = 0;
lastclass = ITEM_CLASS_INVALID;
}
/* if we hit a NOT, make sure we don't have a double */
else if (code == SEQCODE_NOT)
{
if (lastcode == SEQCODE_NOT)
return FALSE;
}
/* anything else */
else
{
input_item_class itemclass = INPUT_CODE_ITEMCLASS(code);
/* count positive codes */
if (lastcode != SEQCODE_NOT)
positive_code_count++;
/* non-switch items can't have a NOT */
if (itemclass != ITEM_CLASS_SWITCH && lastcode == SEQCODE_NOT)
return FALSE;
/* absolute/relative items must all be the same class */
if ((lastclass == ITEM_CLASS_ABSOLUTE && itemclass != ITEM_CLASS_ABSOLUTE) ||
(lastclass == ITEM_CLASS_RELATIVE && itemclass != ITEM_CLASS_RELATIVE))
return FALSE;
}
/* remember the last code */
lastcode = code;
}
/* if we got here, we were missing an END token; fail */
return FALSE;
}