mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2025-01-14 14:20:21 +00:00
bitmask: Don't allocate memory for small bitmasks
Code taken more or less verbatim from CoglBitmask.
This commit is contained in:
parent
27eb83a410
commit
6044dfc35b
@ -31,6 +31,15 @@ struct _GtkBitmask {
|
|||||||
VALUE_TYPE data[1];
|
VALUE_TYPE data[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define ENSURE_ALLOCATED(mask, heap_mask) G_STMT_START { \
|
||||||
|
if (!_gtk_bitmask_is_allocated (mask)) \
|
||||||
|
{ \
|
||||||
|
heap_mask.data[0] = _gtk_bitmask_to_bits (mask); \
|
||||||
|
heap_mask.len = heap_mask.data[0] ? 1 : 0; \
|
||||||
|
mask = &heap_mask; \
|
||||||
|
} \
|
||||||
|
} G_STMT_END
|
||||||
|
|
||||||
static GtkBitmask *
|
static GtkBitmask *
|
||||||
gtk_allocated_bitmask_resize (GtkBitmask *mask,
|
gtk_allocated_bitmask_resize (GtkBitmask *mask,
|
||||||
gsize size) G_GNUC_WARN_UNUSED_RESULT;
|
gsize size) G_GNUC_WARN_UNUSED_RESULT;
|
||||||
@ -40,7 +49,7 @@ gtk_allocated_bitmask_resize (GtkBitmask *mask,
|
|||||||
{
|
{
|
||||||
gsize i;
|
gsize i;
|
||||||
|
|
||||||
mask = g_realloc (mask, sizeof (GtkBitmask) + sizeof(VALUE_TYPE) * (MAX (size, 1) - 1));
|
mask = g_realloc (mask, sizeof (GtkBitmask) + sizeof(VALUE_TYPE) * (size - 1));
|
||||||
|
|
||||||
for (i = mask->len; i < size; i++)
|
for (i = mask->len; i < size; i++)
|
||||||
mask->data[i] = 0;
|
mask->data[i] = 0;
|
||||||
@ -50,10 +59,25 @@ gtk_allocated_bitmask_resize (GtkBitmask *mask,
|
|||||||
return mask;
|
return mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
GtkBitmask *
|
static GtkBitmask *
|
||||||
_gtk_allocated_bitmask_new (void)
|
gtk_allocated_bitmask_new_for_bits (gsize bits)
|
||||||
{
|
{
|
||||||
return g_malloc0 (sizeof (GtkBitmask));
|
GtkBitmask *mask;
|
||||||
|
|
||||||
|
mask = g_malloc (sizeof (GtkBitmask));
|
||||||
|
mask->len = bits ? 1 : 0;
|
||||||
|
mask->data[0] = bits;
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkBitmask *
|
||||||
|
gtk_bitmask_ensure_allocated (GtkBitmask *mask)
|
||||||
|
{
|
||||||
|
if (_gtk_bitmask_is_allocated (mask))
|
||||||
|
return mask;
|
||||||
|
else
|
||||||
|
return gtk_allocated_bitmask_new_for_bits (_gtk_bitmask_to_bits (mask));
|
||||||
}
|
}
|
||||||
|
|
||||||
GtkBitmask *
|
GtkBitmask *
|
||||||
@ -63,7 +87,7 @@ _gtk_allocated_bitmask_copy (const GtkBitmask *mask)
|
|||||||
|
|
||||||
g_return_val_if_fail (mask != NULL, NULL);
|
g_return_val_if_fail (mask != NULL, NULL);
|
||||||
|
|
||||||
copy = _gtk_allocated_bitmask_new ();
|
copy = gtk_allocated_bitmask_new_for_bits (0);
|
||||||
|
|
||||||
return _gtk_allocated_bitmask_union (copy, mask);
|
return _gtk_allocated_bitmask_union (copy, mask);
|
||||||
}
|
}
|
||||||
@ -80,11 +104,14 @@ void
|
|||||||
_gtk_allocated_bitmask_print (const GtkBitmask *mask,
|
_gtk_allocated_bitmask_print (const GtkBitmask *mask,
|
||||||
GString *string)
|
GString *string)
|
||||||
{
|
{
|
||||||
|
GtkBitmask mask_allocated;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
g_return_if_fail (mask != NULL);
|
g_return_if_fail (mask != NULL);
|
||||||
g_return_if_fail (string != NULL);
|
g_return_if_fail (string != NULL);
|
||||||
|
|
||||||
|
ENSURE_ALLOCATED (mask, mask_allocated);
|
||||||
|
|
||||||
for (i = mask->len * VALUE_SIZE_BITS - 1; i >= 0; i--)
|
for (i = mask->len * VALUE_SIZE_BITS - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (_gtk_allocated_bitmask_get (mask, i))
|
if (_gtk_allocated_bitmask_get (mask, i))
|
||||||
@ -103,16 +130,6 @@ _gtk_allocated_bitmask_print (const GtkBitmask *mask,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char *
|
|
||||||
_gtk_allocated_bitmask_to_string (const GtkBitmask *mask)
|
|
||||||
{
|
|
||||||
GString *string;
|
|
||||||
|
|
||||||
string = g_string_new (NULL);
|
|
||||||
_gtk_allocated_bitmask_print (mask, string);
|
|
||||||
return g_string_free (string, FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* NB: Call this function whenever the
|
/* NB: Call this function whenever the
|
||||||
* array might have become too large.
|
* array might have become too large.
|
||||||
* _gtk_allocated_bitmask_is_empty() depends on this.
|
* _gtk_allocated_bitmask_is_empty() depends on this.
|
||||||
@ -130,6 +147,14 @@ gtk_allocated_bitmask_shrink (GtkBitmask *mask)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (i == 0 ||
|
||||||
|
(i == 1 && mask->data[0] < VALUE_BIT (GTK_BITMASK_N_DIRECT_BITS)))
|
||||||
|
{
|
||||||
|
GtkBitmask *result = _gtk_bitmask_from_bits (i == 0 ? 0 : mask->data[0]);
|
||||||
|
_gtk_allocated_bitmask_free (mask);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
return gtk_allocated_bitmask_resize (mask, i);
|
return gtk_allocated_bitmask_resize (mask, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,11 +162,15 @@ GtkBitmask *
|
|||||||
_gtk_allocated_bitmask_intersect (GtkBitmask *mask,
|
_gtk_allocated_bitmask_intersect (GtkBitmask *mask,
|
||||||
const GtkBitmask *other)
|
const GtkBitmask *other)
|
||||||
{
|
{
|
||||||
|
GtkBitmask other_allocated;
|
||||||
guint i;
|
guint i;
|
||||||
|
|
||||||
g_return_val_if_fail (mask != NULL, NULL);
|
g_return_val_if_fail (mask != NULL, NULL);
|
||||||
g_return_val_if_fail (other != NULL, NULL);
|
g_return_val_if_fail (other != NULL, NULL);
|
||||||
|
|
||||||
|
mask = gtk_bitmask_ensure_allocated (mask);
|
||||||
|
ENSURE_ALLOCATED (other, other_allocated);
|
||||||
|
|
||||||
mask = gtk_allocated_bitmask_resize (mask, MIN (mask->len, other->len));
|
mask = gtk_allocated_bitmask_resize (mask, MIN (mask->len, other->len));
|
||||||
for (i = 0; i < mask->len; i++)
|
for (i = 0; i < mask->len; i++)
|
||||||
{
|
{
|
||||||
@ -155,11 +184,15 @@ GtkBitmask *
|
|||||||
_gtk_allocated_bitmask_union (GtkBitmask *mask,
|
_gtk_allocated_bitmask_union (GtkBitmask *mask,
|
||||||
const GtkBitmask *other)
|
const GtkBitmask *other)
|
||||||
{
|
{
|
||||||
|
GtkBitmask other_allocated;
|
||||||
guint i;
|
guint i;
|
||||||
|
|
||||||
g_return_val_if_fail (mask != NULL, NULL);
|
g_return_val_if_fail (mask != NULL, NULL);
|
||||||
g_return_val_if_fail (other != NULL, NULL);
|
g_return_val_if_fail (other != NULL, NULL);
|
||||||
|
|
||||||
|
mask = gtk_bitmask_ensure_allocated (mask);
|
||||||
|
ENSURE_ALLOCATED (other, other_allocated);
|
||||||
|
|
||||||
mask = gtk_allocated_bitmask_resize (mask, MAX (mask->len, other->len));
|
mask = gtk_allocated_bitmask_resize (mask, MAX (mask->len, other->len));
|
||||||
for (i = 0; i < other->len; i++)
|
for (i = 0; i < other->len; i++)
|
||||||
{
|
{
|
||||||
@ -173,11 +206,15 @@ GtkBitmask *
|
|||||||
_gtk_allocated_bitmask_subtract (GtkBitmask *mask,
|
_gtk_allocated_bitmask_subtract (GtkBitmask *mask,
|
||||||
const GtkBitmask *other)
|
const GtkBitmask *other)
|
||||||
{
|
{
|
||||||
|
GtkBitmask other_allocated;
|
||||||
guint i;
|
guint i;
|
||||||
|
|
||||||
g_return_val_if_fail (mask != NULL, NULL);
|
g_return_val_if_fail (mask != NULL, NULL);
|
||||||
g_return_val_if_fail (other != NULL, NULL);
|
g_return_val_if_fail (other != NULL, NULL);
|
||||||
|
|
||||||
|
mask = gtk_bitmask_ensure_allocated (mask);
|
||||||
|
ENSURE_ALLOCATED (other, other_allocated);
|
||||||
|
|
||||||
for (i = 0; i < other->len; i++)
|
for (i = 0; i < other->len; i++)
|
||||||
{
|
{
|
||||||
mask->data[i] |= ~other->data[i];
|
mask->data[i] |= ~other->data[i];
|
||||||
@ -220,6 +257,7 @@ _gtk_allocated_bitmask_set (GtkBitmask *mask,
|
|||||||
|
|
||||||
g_return_val_if_fail (mask != NULL, NULL);
|
g_return_val_if_fail (mask != NULL, NULL);
|
||||||
|
|
||||||
|
mask = gtk_bitmask_ensure_allocated (mask);
|
||||||
gtk_allocated_bitmask_indexes (index_, &array_index, &bit_index);
|
gtk_allocated_bitmask_indexes (index_, &array_index, &bit_index);
|
||||||
|
|
||||||
if (value)
|
if (value)
|
||||||
@ -251,22 +289,15 @@ _gtk_allocated_bitmask_invert_range (GtkBitmask *mask,
|
|||||||
g_return_val_if_fail (mask != NULL, NULL);
|
g_return_val_if_fail (mask != NULL, NULL);
|
||||||
g_return_val_if_fail (start < end, NULL);
|
g_return_val_if_fail (start < end, NULL);
|
||||||
|
|
||||||
/* I CAN HAS SPEEDUP? */
|
mask = gtk_bitmask_ensure_allocated (mask);
|
||||||
|
|
||||||
|
/* I CAN HAS SPEEDUP? */
|
||||||
for (i = start; i < end; i++)
|
for (i = start; i < end; i++)
|
||||||
mask = _gtk_allocated_bitmask_set (mask, i, !_gtk_allocated_bitmask_get (mask, i));
|
mask = _gtk_allocated_bitmask_set (mask, i, !_gtk_allocated_bitmask_get (mask, i));
|
||||||
|
|
||||||
return mask;
|
return mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
|
||||||
_gtk_allocated_bitmask_is_empty (const GtkBitmask *mask)
|
|
||||||
{
|
|
||||||
g_return_val_if_fail (mask != NULL, FALSE);
|
|
||||||
|
|
||||||
return mask->len == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
_gtk_allocated_bitmask_equals (const GtkBitmask *mask,
|
_gtk_allocated_bitmask_equals (const GtkBitmask *mask,
|
||||||
const GtkBitmask *other)
|
const GtkBitmask *other)
|
||||||
@ -292,11 +323,15 @@ gboolean
|
|||||||
_gtk_allocated_bitmask_intersects (const GtkBitmask *mask,
|
_gtk_allocated_bitmask_intersects (const GtkBitmask *mask,
|
||||||
const GtkBitmask *other)
|
const GtkBitmask *other)
|
||||||
{
|
{
|
||||||
|
GtkBitmask mask_allocated, other_allocated;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
g_return_val_if_fail (mask != NULL, FALSE);
|
g_return_val_if_fail (mask != NULL, FALSE);
|
||||||
g_return_val_if_fail (other != NULL, FALSE);
|
g_return_val_if_fail (other != NULL, FALSE);
|
||||||
|
|
||||||
|
ENSURE_ALLOCATED (mask, mask_allocated);
|
||||||
|
ENSURE_ALLOCATED (other, other_allocated);
|
||||||
|
|
||||||
for (i = MIN (mask->len, other->len) - 1; i >= 0; i--)
|
for (i = MIN (mask->len, other->len) - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (mask->data[i] & other->data[i])
|
if (mask->data[i] & other->data[i])
|
||||||
|
@ -27,12 +27,21 @@ G_BEGIN_DECLS
|
|||||||
|
|
||||||
typedef struct _GtkBitmask GtkBitmask;
|
typedef struct _GtkBitmask GtkBitmask;
|
||||||
|
|
||||||
|
#define _gtk_bitmask_to_bits(mask) \
|
||||||
|
(GPOINTER_TO_SIZE (mask) >> ((gsize) 1))
|
||||||
|
|
||||||
|
#define _gtk_bitmask_from_bits(bits) \
|
||||||
|
GSIZE_TO_POINTER ((((gsize) (bits)) << 1) | 1)
|
||||||
|
|
||||||
|
#define _gtk_bitmask_is_allocated(mask) \
|
||||||
|
(!(GPOINTER_TO_SIZE (mask) & 1))
|
||||||
|
|
||||||
|
#define GTK_BITMASK_N_DIRECT_BITS (sizeof (gsize) * 8 - 1)
|
||||||
|
|
||||||
|
|
||||||
GtkBitmask * _gtk_allocated_bitmask_new (void);
|
|
||||||
GtkBitmask * _gtk_allocated_bitmask_copy (const GtkBitmask *mask);
|
GtkBitmask * _gtk_allocated_bitmask_copy (const GtkBitmask *mask);
|
||||||
void _gtk_allocated_bitmask_free (GtkBitmask *mask);
|
void _gtk_allocated_bitmask_free (GtkBitmask *mask);
|
||||||
|
|
||||||
char * _gtk_allocated_bitmask_to_string (const GtkBitmask *mask);
|
|
||||||
void _gtk_allocated_bitmask_print (const GtkBitmask *mask,
|
void _gtk_allocated_bitmask_print (const GtkBitmask *mask,
|
||||||
GString *string);
|
GString *string);
|
||||||
|
|
||||||
@ -53,7 +62,6 @@ GtkBitmask * _gtk_allocated_bitmask_invert_range (GtkBitmask *mask
|
|||||||
guint start,
|
guint start,
|
||||||
guint end) G_GNUC_WARN_UNUSED_RESULT;
|
guint end) G_GNUC_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
gboolean _gtk_allocated_bitmask_is_empty (const GtkBitmask *mask);
|
|
||||||
gboolean _gtk_allocated_bitmask_equals (const GtkBitmask *mask,
|
gboolean _gtk_allocated_bitmask_equals (const GtkBitmask *mask,
|
||||||
const GtkBitmask *other);
|
const GtkBitmask *other);
|
||||||
gboolean _gtk_allocated_bitmask_intersects (const GtkBitmask *mask,
|
gboolean _gtk_allocated_bitmask_intersects (const GtkBitmask *mask,
|
||||||
|
@ -23,25 +23,33 @@
|
|||||||
static inline GtkBitmask *
|
static inline GtkBitmask *
|
||||||
_gtk_bitmask_new (void)
|
_gtk_bitmask_new (void)
|
||||||
{
|
{
|
||||||
return _gtk_allocated_bitmask_new ();
|
return _gtk_bitmask_from_bits (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline GtkBitmask *
|
static inline GtkBitmask *
|
||||||
_gtk_bitmask_copy (const GtkBitmask *mask)
|
_gtk_bitmask_copy (const GtkBitmask *mask)
|
||||||
{
|
{
|
||||||
return _gtk_allocated_bitmask_copy (mask);
|
if (_gtk_bitmask_is_allocated (mask))
|
||||||
|
return _gtk_allocated_bitmask_copy (mask);
|
||||||
|
else
|
||||||
|
return (GtkBitmask *) mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
_gtk_bitmask_free (GtkBitmask *mask)
|
_gtk_bitmask_free (GtkBitmask *mask)
|
||||||
{
|
{
|
||||||
return _gtk_allocated_bitmask_free (mask);
|
if (_gtk_bitmask_is_allocated (mask))
|
||||||
|
return _gtk_allocated_bitmask_free (mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline char *
|
static inline char *
|
||||||
_gtk_bitmask_to_string (const GtkBitmask *mask)
|
_gtk_bitmask_to_string (const GtkBitmask *mask)
|
||||||
{
|
{
|
||||||
return _gtk_allocated_bitmask_to_string (mask);
|
GString *string;
|
||||||
|
|
||||||
|
string = g_string_new (NULL);
|
||||||
|
_gtk_allocated_bitmask_print (mask, string);
|
||||||
|
return g_string_free (string, FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@ -62,7 +70,12 @@ static inline GtkBitmask *
|
|||||||
_gtk_bitmask_union (GtkBitmask *mask,
|
_gtk_bitmask_union (GtkBitmask *mask,
|
||||||
const GtkBitmask *other)
|
const GtkBitmask *other)
|
||||||
{
|
{
|
||||||
return _gtk_allocated_bitmask_union (mask, other);
|
if (_gtk_bitmask_is_allocated (mask) ||
|
||||||
|
_gtk_bitmask_is_allocated (other))
|
||||||
|
return _gtk_allocated_bitmask_union (mask, other);
|
||||||
|
else
|
||||||
|
return _gtk_bitmask_from_bits (_gtk_bitmask_to_bits (mask)
|
||||||
|
| _gtk_bitmask_to_bits (other));
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline GtkBitmask *
|
static inline GtkBitmask *
|
||||||
@ -76,7 +89,12 @@ static inline gboolean
|
|||||||
_gtk_bitmask_get (const GtkBitmask *mask,
|
_gtk_bitmask_get (const GtkBitmask *mask,
|
||||||
guint index_)
|
guint index_)
|
||||||
{
|
{
|
||||||
return _gtk_allocated_bitmask_get (mask, index_);
|
if (_gtk_bitmask_is_allocated (mask))
|
||||||
|
return _gtk_allocated_bitmask_get (mask, index_);
|
||||||
|
else
|
||||||
|
return index_ < GTK_BITMASK_N_DIRECT_BITS
|
||||||
|
? !!(_gtk_bitmask_to_bits (mask) & (((size_t) 1) << index_))
|
||||||
|
: FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline GtkBitmask *
|
static inline GtkBitmask *
|
||||||
@ -84,7 +102,22 @@ _gtk_bitmask_set (GtkBitmask *mask,
|
|||||||
guint index_,
|
guint index_,
|
||||||
gboolean value)
|
gboolean value)
|
||||||
{
|
{
|
||||||
return _gtk_allocated_bitmask_set (mask, index_, value);
|
if (_gtk_bitmask_is_allocated (mask) ||
|
||||||
|
(index_ >= GTK_BITMASK_N_DIRECT_BITS && value))
|
||||||
|
return _gtk_allocated_bitmask_set (mask, index_, value);
|
||||||
|
else if (index_ < GTK_BITMASK_N_DIRECT_BITS)
|
||||||
|
{
|
||||||
|
gsize bits = _gtk_bitmask_to_bits (mask);
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
bits |= ((size_t) 1) << index_;
|
||||||
|
else
|
||||||
|
bits &= ~(((size_t) 1) << index_);
|
||||||
|
|
||||||
|
return _gtk_bitmask_from_bits (bits);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline GtkBitmask *
|
static inline GtkBitmask *
|
||||||
@ -92,19 +125,34 @@ _gtk_bitmask_invert_range (GtkBitmask *mask,
|
|||||||
guint start,
|
guint start,
|
||||||
guint end)
|
guint end)
|
||||||
{
|
{
|
||||||
return _gtk_allocated_bitmask_invert_range (mask, start, end);
|
if (_gtk_bitmask_is_allocated (mask) ||
|
||||||
|
(end > GTK_BITMASK_N_DIRECT_BITS))
|
||||||
|
return _gtk_allocated_bitmask_invert_range (mask, start, end);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t invert = (((size_t) 1) << end) - (((size_t) 1) << start);
|
||||||
|
|
||||||
|
return _gtk_bitmask_from_bits (_gtk_bitmask_to_bits (mask) ^ invert);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline gboolean
|
static inline gboolean
|
||||||
_gtk_bitmask_is_empty (const GtkBitmask *mask)
|
_gtk_bitmask_is_empty (const GtkBitmask *mask)
|
||||||
{
|
{
|
||||||
return _gtk_allocated_bitmask_is_empty (mask);
|
return mask == _gtk_bitmask_from_bits (0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline gboolean
|
static inline gboolean
|
||||||
_gtk_bitmask_equals (const GtkBitmask *mask,
|
_gtk_bitmask_equals (const GtkBitmask *mask,
|
||||||
const GtkBitmask *other)
|
const GtkBitmask *other)
|
||||||
{
|
{
|
||||||
|
if (mask == other)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
if (!_gtk_bitmask_is_allocated (mask) ||
|
||||||
|
!_gtk_bitmask_is_allocated (other))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
return _gtk_allocated_bitmask_equals (mask, other);
|
return _gtk_allocated_bitmask_equals (mask, other);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,5 +160,9 @@ static inline gboolean
|
|||||||
_gtk_bitmask_intersects (const GtkBitmask *mask,
|
_gtk_bitmask_intersects (const GtkBitmask *mask,
|
||||||
const GtkBitmask *other)
|
const GtkBitmask *other)
|
||||||
{
|
{
|
||||||
return _gtk_allocated_bitmask_intersects (mask, other);
|
if (!_gtk_bitmask_is_allocated (mask) ||
|
||||||
|
!_gtk_bitmask_is_allocated (other))
|
||||||
|
return _gtk_allocated_bitmask_intersects (mask, other);
|
||||||
|
else
|
||||||
|
return _gtk_bitmask_to_bits (mask) & _gtk_bitmask_to_bits (other);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user