Skip to content

Commit

Permalink
feat: reduce overhead to remove callbacks by using sets to store call…
Browse files Browse the repository at this point in the history
…backs (#27)
  • Loading branch information
bdraco authored Jan 21, 2024
1 parent 2c2364b commit 05ceb85
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 33 deletions.
6 changes: 3 additions & 3 deletions src/habluetooth/manager.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ cdef class BluetoothManager:
cdef public dict _intervals
cdef public dict _unavailable_callbacks
cdef public dict _connectable_unavailable_callbacks
cdef public list _bleak_callbacks
cdef public set _bleak_callbacks
cdef public dict _all_history
cdef public dict _connectable_history
cdef public list _non_connectable_scanners
cdef public list _connectable_scanners
cdef public set _non_connectable_scanners
cdef public set _connectable_scanners
cdef public dict _adapters
cdef public dict _sources
cdef public object _bluetooth_adapters
Expand Down
79 changes: 49 additions & 30 deletions src/habluetooth/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import itertools
import logging
from collections.abc import Callable, Iterable
from functools import partial
from typing import TYPE_CHECKING, Any, Final

from bleak.backends.scanner import AdvertisementDataCallback
Expand Down Expand Up @@ -128,17 +129,17 @@ def __init__(
self._intervals = self._advertisement_tracker.intervals

self._unavailable_callbacks: dict[
str, list[Callable[[BluetoothServiceInfoBleak], None]]
str, set[Callable[[BluetoothServiceInfoBleak], None]]
] = {}
self._connectable_unavailable_callbacks: dict[
str, list[Callable[[BluetoothServiceInfoBleak], None]]
str, set[Callable[[BluetoothServiceInfoBleak], None]]
] = {}

self._bleak_callbacks: list[BleakCallback] = []
self._bleak_callbacks: set[BleakCallback] = set()
self._all_history: dict[str, BluetoothServiceInfoBleak] = {}
self._connectable_history: dict[str, BluetoothServiceInfoBleak] = {}
self._non_connectable_scanners: list[BaseHaScanner] = []
self._connectable_scanners: list[BaseHaScanner] = []
self._non_connectable_scanners: set[BaseHaScanner] = set()
self._connectable_scanners: set[BaseHaScanner] = set()
self._adapters: dict[str, AdapterDetails] = {}
self._sources: dict[str, BaseHaScanner] = {}
self._bluetooth_adapters = bluetooth_adapters
Expand Down Expand Up @@ -556,6 +557,20 @@ def _async_describe_source(self, service_info: BluetoothServiceInfoBleak) -> str
description += " [connectable]"
return description

def _async_remove_unavailable_callback_internal(
self,
unavailable_callbacks: dict[
str, set[Callable[[BluetoothServiceInfoBleak], None]]
],
address: str,
callbacks: set[Callable[[BluetoothServiceInfoBleak], None]],
callback: Callable[[BluetoothServiceInfoBleak], None],
) -> None:
"""Remove a callback."""
callbacks.remove(callback)
if not callbacks:
del unavailable_callbacks[address]

def async_track_unavailable(
self,
callback: Callable[[BluetoothServiceInfoBleak], None],
Expand All @@ -567,14 +582,15 @@ def async_track_unavailable(
unavailable_callbacks = self._connectable_unavailable_callbacks
else:
unavailable_callbacks = self._unavailable_callbacks
unavailable_callbacks.setdefault(address, []).append(callback)

def _async_remove_callback() -> None:
unavailable_callbacks[address].remove(callback)
if not unavailable_callbacks[address]:
del unavailable_callbacks[address]

return _async_remove_callback
callbacks = unavailable_callbacks.setdefault(address, set())
callbacks.add(callback)
return partial(
self._async_remove_unavailable_callback_internal,
unavailable_callbacks,
address,
callbacks,
callback,
)

def async_ble_device_from_address(
self, address: str, connectable: bool
Expand Down Expand Up @@ -604,6 +620,20 @@ def async_last_service_info(
histories = self._connectable_history if connectable else self._all_history
return histories.get(address)

def _async_unregister_scanner_internal(
self,
scanners: set[BaseHaScanner],
scanner: BaseHaScanner,
connection_slots: int | None,
) -> None:
"""Unregister a scanner."""
_LOGGER.debug("Unregistering scanner %s", scanner.name)
self._advertisement_tracker.async_remove_source(scanner.source)
scanners.remove(scanner)
del self._sources[scanner.source]
if connection_slots:
self.slot_manager.remove_adapter(scanner.adapter)

def async_register_scanner(
self,
scanner: BaseHaScanner,
Expand All @@ -615,31 +645,20 @@ def async_register_scanner(
scanners = self._connectable_scanners
else:
scanners = self._non_connectable_scanners

def _unregister_scanner() -> None:
_LOGGER.debug("Unregistering scanner %s", scanner.name)
self._advertisement_tracker.async_remove_source(scanner.source)
scanners.remove(scanner)
del self._sources[scanner.source]
if connection_slots:
self.slot_manager.remove_adapter(scanner.adapter)

scanners.append(scanner)
scanners.add(scanner)
self._sources[scanner.source] = scanner
if connection_slots:
self.slot_manager.register_adapter(scanner.adapter, connection_slots)
return _unregister_scanner
return partial(
self._async_unregister_scanner_internal, scanners, scanner, connection_slots
)

def async_register_bleak_callback(
self, callback: AdvertisementDataCallback, filters: dict[str, set[str]]
) -> CALLBACK_TYPE:
"""Register a callback."""
callback_entry = BleakCallback(callback, filters)
self._bleak_callbacks.append(callback_entry)

def _remove_callback() -> None:
self._bleak_callbacks.remove(callback_entry)

self._bleak_callbacks.add(callback_entry)
# Replay the history since otherwise we miss devices
# that were already discovered before the callback was registered
# or we are in passive mode
Expand All @@ -648,7 +667,7 @@ def _remove_callback() -> None:
callback, filters, history.device, history.advertisement
)

return _remove_callback
return partial(self._bleak_callbacks.remove, callback_entry)

def async_release_connection_slot(self, device: BLEDevice) -> None:
"""Release a connection slot."""
Expand Down

0 comments on commit 05ceb85

Please sign in to comment.