Exemplo n.º 1
0
def test_creation():
    a_table = AssociativeTable()
    a_table = AssociativeTable('foo', 'bar', 'baz')
    assert hasattr(a_table, 'foo')
    assert hasattr(a_table, 'bar')
    assert hasattr(a_table, 'baz')
    assert a_table._column['foo'] is a_table.foo
    assert a_table._column['bar'] is a_table.bar
    assert a_table._column['baz'] is a_table.baz
Exemplo n.º 2
0
def test_delete_complex():
    element_a = 'a'
    element_b = 'b'
    a_table = AssociativeTable('foo', 'bar')
    a_table.foo.add(element_a)
    a_table.bar.add(element_b)
    a_table._assoc(element_a, element_b)
    del a_table[element_b]
    assert a_table[element_a] == set()
Exemplo n.º 3
0
def test_path():
    elem_a = 'a'
    elem_b = 'b'
    elem_c = 'c'
    a_table = AssociativeTable('foo', 'bar', 'baz')
    a_table.foo.add(elem_a)
    a_table.bar.add(elem_b)
    a_table.baz.add(elem_c)
    a_table._assoc(elem_a, elem_b)
    a_table._assoc(elem_b, elem_c)
    assert a_table.get(elem_a, path=(a_table.bar, a_table.baz)) == set(elem_c)
Exemplo n.º 4
0
def test_get():
    element = 'foo'
    a_table = AssociativeTable('foo')
    with pytest.raises(KeyError):
        a_table[element]
    with pytest.raises(KeyError):
        a_table.get(element)
    with pytest.raises(KeyError):
        a_table.foo[element]
    with pytest.raises(KeyError):
        a_table.foo.get(element)
    a_table.foo.add(element)
    assert a_table[element] == set()
    assert a_table.foo[element] == set()
Exemplo n.º 5
0
def test_association():
    elem_aa = 'aa'
    elem_ab = 'ab'
    elem_ba = 'ba'
    elem_bb = 'bb'
    a_table = AssociativeTable('foo', 'bar')
    a_table.foo.add(elem_aa)
    a_table.foo.add(elem_ab)
    a_table.bar.add(elem_ba)
    a_table.bar.add(elem_bb)
    a_table._assoc(elem_aa, elem_ba)
    a_table._assoc(elem_ab, elem_bb)
    assert a_table[elem_aa] == set([elem_ba])
    assert a_table[elem_bb] == set([elem_ab])
    assert a_table.get(elem_aa, elem_ab) == set([elem_ba, elem_bb])
Exemplo n.º 6
0
def test_presence():
    element = 'a'
    a_table = AssociativeTable('foo')
    assert element not in a_table
    a_table.foo.add(element)
    assert element in a_table
    assert element in a_table.foo
Exemplo n.º 7
0
def test_delete_simple():
    element = 'a'
    a_table = AssociativeTable('foo')
    with pytest.raises(KeyError):
        del a_table[element]
    a_table.foo.add(element)
    del a_table[element]
    assert element not in a_table
Exemplo n.º 8
0
def test_dissociation():
    elem_a = 'a'
    elem_b = 'b'
    a_table = AssociativeTable('foo', 'bar')
    a_table.foo.add(elem_a)
    a_table.foo.add(elem_b)
    a_table._assoc(elem_a, elem_b)
    a_table._dissoc(elem_a, elem_b)
    assert a_table[elem_a] == set()
    assert a_table[elem_b] == set()
Exemplo n.º 9
0
def test_association_with_filter():
    elem_a = 'a'
    elem_b = 'b'
    elem_c = 'c'
    a_table = AssociativeTable('foo', 'bar', 'baz')
    a_table.foo.add(elem_a)
    a_table.bar.add(elem_b)
    a_table.baz.add(elem_c)
    a_table._assoc(elem_a, elem_b)
    a_table._assoc(elem_b, elem_c)
    assert a_table[elem_b] == set([elem_a, elem_c])
    assert a_table.get(elem_b, tables=(a_table.foo, )) == set(elem_a)
    assert a_table.get(elem_b, tables=(a_table.baz, )) == set(elem_c)
    assert a_table.get(elem_b, tables=(a_table.foo, a_table.baz)) == \
        set([elem_a, elem_c])
Exemplo n.º 10
0
 def __init__(self, dclasses):
     self.dclasses = [dclasses[dclass_name]
                      for dclass_name in sorted(dclasses)]
     for dclass_id, dclass in enumerate(self.dclasses):
         dclass.dclass_id = dclass_id
     self.state = AssociativeTable('recipients', 'zones', 'dobjects')
     # FIXME: Recipients and zones should be BijectiveMaps as well.
     self.recipients = AssociativeTable('r_id', 'r_object')
     self.zones = AssociativeTable('z_id', 'z_object')
     self.dobjects = BijectiveMap()
     # TODO: For the moment, we'll use a single lock to protect the whole of
     # the state; dobject existence, presence in zones, and interest. This is
     # a topic ripe for optimization, if you can do it without creating
     # deadlocks. Maybe do optimistic concurrency? Have a nifty planner
     # that'll schedule event processing into isolated parallelity?
     self.state_lock = Lock()
     self.emission_queue = Queue()
Exemplo n.º 11
0
def test_unique_elements():
    element = 'a'
    a_table = AssociativeTable('foo', 'bar')
    a_table.foo.add(element)
    with pytest.raises(ValueError):
        a_table.bar.add(element)
Exemplo n.º 12
0
class SimpleStateKeeper(BaseStateKeeper):
    def __init__(self, dclasses):
        self.dclasses = [dclasses[dclass_name]
                         for dclass_name in sorted(dclasses)]
        for dclass_id, dclass in enumerate(self.dclasses):
            dclass.dclass_id = dclass_id
        self.state = AssociativeTable('recipients', 'zones', 'dobjects')
        # FIXME: Recipients and zones should be BijectiveMaps as well.
        self.recipients = AssociativeTable('r_id', 'r_object')
        self.zones = AssociativeTable('z_id', 'z_object')
        self.dobjects = BijectiveMap()
        # TODO: For the moment, we'll use a single lock to protect the whole of
        # the state; dobject existence, presence in zones, and interest. This is
        # a topic ripe for optimization, if you can do it without creating
        # deadlocks. Maybe do optimistic concurrency? Have a nifty planner
        # that'll schedule event processing into isolated parallelity?
        self.state_lock = Lock()
        self.emission_queue = Queue()

    def get_dobject_fields(self, dobject_id):
        with self.state_lock:
            dobject = self.dobjects[dobject_id].dfields
        return dobject

    def _queue_message(self, *message):
        # FIXME: Make sure that all data is copies.
        self.emission_queue.put(message)

    def _work_emission_queue(self):
        working = True
        while working:
            try:
                message = self.emission_queue.get(block=False)
                self.message_director.create_message(*message)
            except Empty:
                working = False

    def emit_create_dobject_view(self, recipients, dobject_maps):
        for recipient in recipients:
            for dobject_id, dobject in dobject_maps:
                self._queue_message(
                    self.individual_channel,
                    recipient,
                    msgtypes.CREATE_DOBJECT_VIEW,
                    dobject_id,
                    dobject.dclass_id,
                    dobject.storage,
                )

    def emit_destroy_dobject_view(self, recipients, dobject_ids):
        for recipient in recipients:
            for dobject_id in dobject_ids:
                self._queue_message(
                    self.individual_channel,
                    recipient,
                    msgtypes.DESTROY_DOBJECT_VIEW,
                    dobject_id,
                )

    def create_recipient(self, recipient_id):
        with self.state_lock:
            recipient = Recipient(recipient_id)
            self.recipients.r_id.add(recipient_id)
            self.recipients.r_object.add(recipient)
            self.recipients._assoc(recipient_id, recipient)
            self.state.recipients.add(recipient)

    def create_dobject(self, dobject_id, dclass_id, fields):
        with self.state_lock:
            dclass = self.dclasses[dclass_id]
            dobject = dclass(
                dobject_id,
                fields,
            )
            self.dobjects[dobject_id] = dobject
            self.state.dobjects.add(dobject)

    def create_zone(self, zone_id):
        with self.state_lock:
            zone = Zone(zone_id)
            self.zones.z_id.add(zone_id)
            self.zones.z_object.add(zone)
            self.zones._assoc(zone_id, zone)
            self.state.zones.add(zone)

    def _dobjects_seen(self, recipient):
        return self.state.get(
            recipient,
            path=(self.state.zones, self.state.dobjects),
        )

    def _dobject_seen_by(self, dobject):
        return self.state.get(
            dobject,
            path=(self.state.zones, self.state.recipients),
        )

    def _dobject_to_emittable(self, dobject):
        dobject_id = self.dobjects.getreverse(dobject)
        return (dobject_id, dobject)

    def _dobject_id_to_emittable(self, dobject_id):
        dobject = self.dobjects[dobject_id]
        return (dobject_id, dobject)

    def set_interest(self, recipient_id, zone_id):
        if recipient_id not in self.recipients:
            self.create_recipient(recipient_id)
        if zone_id not in self.zones:
            self.create_zone(zone_id)
        with self.state_lock:
            (recipient, ) = self.recipients[recipient_id]
            (zone, ) = self.zones[zone_id]
            dobjects_before = self._dobjects_seen(recipient)
            self.state._assoc(recipient, zone)
            dobjects_after = self._dobjects_seen(recipient)
            new_dobjects = dobjects_after - dobjects_before
            emittables = [self._dobject_to_emittable(dobject)
                          for dobject in new_dobjects]
            self.emit_create_dobject_view([recipient_id], emittables)
        self._work_emission_queue()
        return [new_dobject_id for (new_dobject_id, _) in emittables]

    def unset_interest(self, recipient_id, zone_id):
        with self.state_lock:
            (recipient, ) = self.recipients[recipient_id]
            (zone, ) = self.zones[zone_id]
            dobjects_before = self._dobjects_seen(recipient)
            self.state._dissoc(recipient, zone)
            dobjects_after = self._dobjects_seen(recipient)
            lost_dobjects = dobjects_before - dobjects_after
            self.emit_destroy_dobject_view([recipient_id], lost_dobjects)
        self._work_emission_queue()
        return lost_dobject_ids

    def add_presence(self, dobject_id, zone_id):
        if zone_id not in self.zones:
            self.create_zone(zone_id)
        with self.state_lock:
            dobject = self.dobjects[dobject_id]
            (zone, ) = self.zones[zone_id]
            recipients_before = self._dobject_seen_by(dobject)
            self.state._assoc(dobject, zone)
            recipients_after = self._dobject_seen_by(dobject)
            new_recipients = recipients_after - recipients_before
            new_recipient_ids = {next(iter(self.recipients[nr]))
                                 for nr in new_recipients}
            emittable = [self._dobject_id_to_emittable(dobject_id)]
            self.emit_create_dobject_view(new_recipient_ids, emittable)
        self._work_emission_queue()
        return new_recipient_ids

    def remove_presence(self, dobject_id, zone_id):
        with self.state_lock:
            dobject = self.dobjects[dobject_id]
            (zone, ) = self.zones[zone_id]
            recipients_before = self._dobject_seen_by(dobject)
            self.state._dissoc(dobject, zone)
            recipients_after = self._dobject_seen_by(dobject)
            lost_recipients = recipients_before - recipients_after
            lost_recipient_ids = {next(iter(self.recipients[nr]))
                                  for nr in lost_recipients}
            self.emit_destroy_dobject_view(lost_recipient_ids, [dobject_id])
        self._work_emission_queue()
        return lost_recipient_ids

    def set_ai(self, ai_channel, dobject_id):
        with self.state_lock:
            self.dobjects[dobject_id].set_ai(ai_channel)
        # self.message_director.create_message(
        #     self.all_connections,  # FIXME: This individual StateServer's ID
        #     ai_channel,
        #     msgtypes.CREATE_AI_VIEW,
        #     dobject_id,  # FIXME: Either we also need to add all state
        #                  # information, or, better (because later updates)
        #                  # assure that the dobject is already in the AI's
        #                  # interest.
        # )

    def set_owner(self, owner_channel, dobject_id):
        with self.state_lock:
            # TODO: Destroy owner view if another owner was set.
            # TODO: Check whether dobject is even visible to client
            self.dobjects[dobject_id].set_owner(owner_channel)
            self._queue_message(
                self.all_connections,  # FIXME: This individual StateServer's ID
                owner_channel,
                msgtypes.BECOME_OWNER,
                dobject_id,
            )
        self._work_emission_queue()


    def set_field(self, source, dobject_id, field_id, value):
        with self.state_lock:
            dobject = self.dobjects[dobject_id]
            # FIXME: Jam this into DClass somehow?
            _, field_type, policy = dobject._dfields[field_id]
            # Is the source even allowed to set this field?
            if (policy & fp.CLIENT_SEND) or \
               ((policy & fp.OWNER_SEND) and source == dobject.owner) or \
               ((policy & fp.AI_SEND) and source == dobject.ai):
                # If it's a storage field, set its value
                if policy & (fp.RAM | fp.PERSIST):
                    pass  # FIXME
                # Emit
                if policy & fp.CLIENT_RECEIVE:
                    for recipient in self._dobject_seen_by(dobject):
                        self._queue_message(
                            source,
                            recipient,
                            msgtypes.FIELD_UPDATE,
                            dobject_id,
                            field_id,
                            value
                        )
                elif policy & fp.OWNER_RECEIVE:
                    self._queue_message(
                        source,
                        dobject.owner,
                        msgtypes.FIELD_UPDATE,
                        dobject_id,
                        field_id,
                        value
                    )
                elif policy & fp.AI_RECEIVE:
                    self._queue_message(
                        source,
                        dobject.ai,
                        msgtypes.FIELD_UPDATE,
                        dobject_id,
                        field_id,
                        value
                    )
                # NOTE: else? I mean, there MUST be a sending policy?
            else:
                raise Exception("{} tried to set {} field {} to {}. "
                                "".format(source, dobject, field_id, value))
        self._work_emission_queue()