Additional plugin usage pooling/isolating

This commit is contained in:
Kelvin 2023-10-10 21:18:13 +02:00
commit 3a7e477e9b
3 changed files with 35 additions and 9 deletions

View file

@ -16,6 +16,8 @@ import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.IPlatformClient
import com.futo.platformplayer.api.media.PlatformMultiClientPool
import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
@ -69,6 +71,8 @@ fun String.isHexColor(): Boolean {
return _regexHexColor.matches(this); return _regexHexColor.matches(this);
} }
fun IPlatformClient.fromPool(pool: PlatformMultiClientPool) = pool.getClientPooled(this);
fun IPlatformVideo.withTimestamp(sec: Long) = PlatformVideoWithTime(this, sec); fun IPlatformVideo.withTimestamp(sec: Long) = PlatformVideoWithTime(this, sec);

View file

@ -4,13 +4,17 @@ class PlatformMultiClientPool {
private val _maxCap: Int; private val _maxCap: Int;
private val _clientPools: HashMap<IPlatformClient, PlatformClientPool> = hashMapOf(); private val _clientPools: HashMap<IPlatformClient, PlatformClientPool> = hashMapOf();
private var _isFake = false;
constructor(maxCap: Int = -1) { constructor(maxCap: Int = -1) {
_maxCap = if(maxCap > 0) _maxCap = if(maxCap > 0)
maxCap maxCap
else 99; else 99;
} }
fun getClientPooled(parentClient: IPlatformClient, capacity: Int): IPlatformClient { fun getClientPooled(parentClient: IPlatformClient, capacity: Int = _maxCap): IPlatformClient {
if(_isFake)
return parentClient;
val pool = synchronized(_clientPools) { val pool = synchronized(_clientPools) {
if(!_clientPools.containsKey(parentClient)) if(!_clientPools.containsKey(parentClient))
_clientPools[parentClient] = PlatformClientPool(parentClient).apply { _clientPools[parentClient] = PlatformClientPool(parentClient).apply {
@ -25,4 +29,10 @@ class PlatformMultiClientPool {
}; };
return pool.getClient(capacity.coerceAtMost(_maxCap)); return pool.getClient(capacity.coerceAtMost(_maxCap));
} }
//Allows for testing disabling pooling without changing callers
fun asFake(): PlatformMultiClientPool {
_isFake = true;
return this;
}
} }

View file

@ -31,6 +31,7 @@ import com.futo.platformplayer.awaitFirstNotNullDeferred
import com.futo.platformplayer.constructs.BatchedTaskHandler import com.futo.platformplayer.constructs.BatchedTaskHandler
import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.fromPool
import com.futo.platformplayer.getNowDiffDays import com.futo.platformplayer.getNowDiffDays
import com.futo.platformplayer.getNowDiffSeconds import com.futo.platformplayer.getNowDiffSeconds
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
@ -63,8 +64,18 @@ class StatePlatform {
private val _availableClients : ArrayList<IPlatformClient> = ArrayList(); private val _availableClients : ArrayList<IPlatformClient> = ArrayList();
private val _enabledClients : ArrayList<IPlatformClient> = ArrayList(); private val _enabledClients : ArrayList<IPlatformClient> = ArrayList();
private val _channelClientPool = PlatformMultiClientPool(15); //ClientPools are used to isolate plugin usage of certain components from others
private val _trackerClientPool = PlatformMultiClientPool(1); //This prevents for example a background task like subscriptions from blocking a user from opening a video
//It also allows parallel usage of plugins that would otherwise be impossible.
//Pools always follow the behavior of the base client. So if user disables a plugin, it kills all pooled clients.
//Each pooled client adds additional memory usage.
//WARNING: Be careful with pooling some calls, as they might use the plugin subsequently afterwards. For example pagers might block plugins in future calls.
private val _mainClientPool = PlatformMultiClientPool(2); //Used for all main user events, generally user critical
private val _pagerClientPool = PlatformMultiClientPool(2); //Used primarily for calls that result in front-end pagers, preventing them from blocking other calls.
private val _channelClientPool = PlatformMultiClientPool(15); //Used primarily for subscription/background channel fetches
private val _trackerClientPool = PlatformMultiClientPool(1); //Used exclusively for playback trackers
private val _liveEventClientPool = PlatformMultiClientPool(1); //Used exclusively for live events
private val _primaryClientPersistent = FragmentedStorage.get<StringStorage>("primaryClient"); private val _primaryClientPersistent = FragmentedStorage.get<StringStorage>("primaryClient");
private var _primaryClientObj : IPlatformClient? = null; private var _primaryClientObj : IPlatformClient? = null;
@ -86,8 +97,9 @@ class StatePlatform {
private val _batchTaskGetVideoDetails: BatchedTaskHandler<String, IPlatformContentDetails> = BatchedTaskHandler<String, IPlatformContentDetails>(_scope, private val _batchTaskGetVideoDetails: BatchedTaskHandler<String, IPlatformContentDetails> = BatchedTaskHandler<String, IPlatformContentDetails>(_scope,
{ url -> { url ->
Logger.i(StatePlatform::class.java.name, "Fetching video details [${url}]"); Logger.i(StatePlatform::class.java.name, "Fetching video details [${url}]");
_enabledClients.find { it.isContentDetailsUrl(url) }?.getContentDetails(url) _enabledClients.find { it.isContentDetailsUrl(url) }?.let {
?: throw NoPlatformClientException("No client enabled that supports this url ($url)"); _mainClientPool.getClientPooled(it).getContentDetails(url)
} ?: throw NoPlatformClientException("No client enabled that supports this url ($url)");
}, },
{ {
if(!Settings.instance.browsing.videoCache) if(!Settings.instance.browsing.videoCache)
@ -363,7 +375,7 @@ class StatePlatform {
synchronized(clientIdsOngoing) { synchronized(clientIdsOngoing) {
clientIdsOngoing.add(it.id); clientIdsOngoing.add(it.id);
} }
val homeResult = it.getHome(); val homeResult = it.fromPool(_pagerClientPool).getHome();
synchronized(clientIdsOngoing) { synchronized(clientIdsOngoing) {
clientIdsOngoing.remove(it.id); clientIdsOngoing.remove(it.id);
} }
@ -383,7 +395,7 @@ class StatePlatform {
val deferred: List<Pair<IPlatformClient, Deferred<IPager<IPlatformContent>?>>> = clients.map { val deferred: List<Pair<IPlatformClient, Deferred<IPager<IPlatformContent>?>>> = clients.map {
return@map Pair(it, scope.async(Dispatchers.IO) { return@map Pair(it, scope.async(Dispatchers.IO) {
try { try {
val searchResult = it.getHome(); val searchResult = it.fromPool(_pagerClientPool).getHome();
return@async searchResult; return@async searchResult;
} catch(ex: Throwable) { } catch(ex: Throwable) {
Logger.e(TAG, "getHomeRefresh", ex); Logger.e(TAG, "getHomeRefresh", ex);
@ -774,7 +786,7 @@ class StatePlatform {
if(!client.capabilities.hasGetComments) if(!client.capabilities.hasGetComments)
return EmptyPager(); return EmptyPager();
return client.getComments(url); return client.fromPool(_mainClientPool).getComments(url);
} }
fun getSubComments(comment: IPlatformComment): IPager<IPlatformComment> { fun getSubComments(comment: IPlatformComment): IPager<IPlatformComment> {
Logger.i(TAG, "Platform - getSubComments"); Logger.i(TAG, "Platform - getSubComments");
@ -785,7 +797,7 @@ class StatePlatform {
fun getLiveEvents(url: String): IPager<IPlatformLiveEvent>? { fun getLiveEvents(url: String): IPager<IPlatformLiveEvent>? {
Logger.i(TAG, "Platform - getLiveChat"); Logger.i(TAG, "Platform - getLiveChat");
var client = getContentClient(url); var client = getContentClient(url);
return client.getLiveEvents(url); return client.fromPool(_liveEventClientPool).getLiveEvents(url);
} }
fun getLiveChatWindow(url: String): ILiveChatWindowDescriptor? { fun getLiveChatWindow(url: String): ILiveChatWindowDescriptor? {
Logger.i(TAG, "Platform - getLiveChat"); Logger.i(TAG, "Platform - getLiveChat");