Improve conversation open speed.

Co-authored-by: Cody Henthorne <cody@signal.org>
This commit is contained in:
Greyson Parrelli
2022-03-16 10:10:01 -04:00
committed by Cody Henthorne
parent d3049a3433
commit 666218773c
27 changed files with 462 additions and 395 deletions

View File

@@ -1,9 +1,7 @@
package org.signal.paging;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -24,16 +22,19 @@ class BufferedPagingController<Key, Data> implements PagingController<Key> {
private final PagedDataSource<Key, Data> dataSource;
private final PagingConfig config;
private final MutableLiveData<List<Data>> liveData;
private final DataStream<Data> dataStream;
private final Executor serializationExecutor;
private PagingController<Key> activeController;
private int lastRequestedIndex;
BufferedPagingController(PagedDataSource<Key, Data> dataSource, PagingConfig config, @NonNull MutableLiveData<List<Data>> liveData) {
BufferedPagingController(@NonNull PagedDataSource<Key, Data> dataSource,
@NonNull PagingConfig config,
@NonNull DataStream<Data> dataStream)
{
this.dataSource = dataSource;
this.config = config;
this.liveData = liveData;
this.dataStream = dataStream;
this.serializationExecutor = Executors.newSingleThreadExecutor();
this.activeController = null;
@@ -57,7 +58,7 @@ class BufferedPagingController<Key, Data> implements PagingController<Key> {
activeController.onDataInvalidated();
}
activeController = new FixedSizePagingController<>(dataSource, config, liveData, dataSource.size());
activeController = new FixedSizePagingController<>(dataSource, config, dataStream, dataSource.size());
activeController.onDataNeededAroundIndex(lastRequestedIndex);
});
}

View File

@@ -0,0 +1,10 @@
package org.signal.paging;
import java.util.List;
/**
* An abstraction over different types of ways the paging lib can provide data, e.g. Observables vs LiveData.
*/
interface DataStream<Data> {
void next(List<Data> data);
}

View File

@@ -1,7 +1,6 @@
package org.signal.paging;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
@@ -29,7 +28,7 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
private final PagedDataSource<Key, Data> dataSource;
private final PagingConfig config;
private final MutableLiveData<List<Data>> liveData;
private final DataStream<Data> dataStream;
private final DataStatus loadState;
private final Map<Key, Integer> keyToPosition;
@@ -39,15 +38,17 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
FixedSizePagingController(@NonNull PagedDataSource<Key, Data> dataSource,
@NonNull PagingConfig config,
@NonNull MutableLiveData<List<Data>> liveData,
@NonNull DataStream<Data> dataStream,
int size)
{
this.dataSource = dataSource;
this.config = config;
this.liveData = liveData;
this.dataStream = dataStream;
this.loadState = DataStatus.obtain(size);
this.data = new CompressedList<>(loadState.size());
this.keyToPosition = new HashMap<>();
if (DEBUG) Log.d(TAG, "[Constructor] Creating with size " + size + " (loadState.size() = " + loadState.size() + ")");
}
/**
@@ -58,7 +59,7 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
@Override
public void onDataNeededAroundIndex(int aroundIndex) {
if (invalidated) {
Log.w(TAG, buildLog(aroundIndex, "Invalidated! At very beginning."));
Log.w(TAG, buildDataNeededLog(aroundIndex, "Invalidated! At very beginning."));
return;
}
@@ -67,7 +68,7 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
synchronized (loadState) {
if (loadState.size() == 0) {
liveData.postValue(Collections.emptyList());
dataStream.next(Collections.emptyList());
return;
}
@@ -81,14 +82,14 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
loadStart = loadState.getEarliestUnmarkedIndexInRange(leftLoadBoundary, rightLoadBoundary);
if (loadStart < 0) {
if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "loadStart < 0"));
if (DEBUG) Log.i(TAG, buildDataNeededLog(aroundIndex, "loadStart < 0"));
return;
}
loadEnd = loadState.getLatestUnmarkedIndexInRange(Math.max(leftLoadBoundary, loadStart), rightLoadBoundary) + 1;
if (loadEnd <= loadStart) {
if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "loadEnd <= loadStart, loadEnd: " + loadEnd + ", loadStart: " + loadStart));
if (DEBUG) Log.i(TAG, buildDataNeededLog(aroundIndex, "loadEnd <= loadStart, loadEnd: " + loadEnd + ", loadStart: " + loadStart));
return;
}
@@ -96,19 +97,19 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
loadState.markRange(loadStart, loadEnd);
if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "start: " + loadStart + ", end: " + loadEnd + ", totalSize: " + totalSize));
if (DEBUG) Log.i(TAG, buildDataNeededLog(aroundIndex, "start: " + loadStart + ", end: " + loadEnd + ", totalSize: " + totalSize));
}
FETCH_EXECUTOR.execute(() -> {
if (invalidated) {
Log.w(TAG, buildLog(aroundIndex, "Invalidated! At beginning of load task."));
Log.w(TAG, buildDataNeededLog(aroundIndex, "Invalidated! At beginning of load task."));
return;
}
List<Data> loaded = dataSource.load(loadStart, loadEnd - loadStart, () -> invalidated);
if (invalidated) {
Log.w(TAG, buildLog(aroundIndex, "Invalidated! Just after data was loaded."));
Log.w(TAG, buildDataNeededLog(aroundIndex, "Invalidated! Just after data was loaded."));
return;
}
@@ -123,7 +124,7 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
}
data = updated;
liveData.postValue(updated);
dataStream.next(updated);
});
}
@@ -139,6 +140,8 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
@Override
public void onDataItemChanged(Key key) {
if (DEBUG) Log.d(TAG, buildItemChangedLog(key, ""));
FETCH_EXECUTOR.execute(() -> {
Integer position = keyToPosition.get(key);
@@ -172,12 +175,16 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
updatedList.set(position, item);
data = updatedList;
liveData.postValue(updatedList);
dataStream.next(updatedList);
if (DEBUG) Log.d(TAG, buildItemChangedLog(key, "Published updated data"));
});
}
@Override
public void onDataItemInserted(Key key, int position) {
if (DEBUG) Log.d(TAG, buildItemInsertedLog(key, position, ""));
FETCH_EXECUTOR.execute(() -> {
if (keyToPosition.containsKey(key)) {
Log.w(TAG, "Notified of key " + key + " being inserted at " + position + ", but the item already exists!");
@@ -191,6 +198,7 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
synchronized (loadState) {
loadState.insertState(position, true);
if (DEBUG) Log.d(TAG, buildItemInsertedLog(key, position, "Size of loadState updated to " + loadState.size()));
}
Data item = dataSource.load(key);
@@ -211,7 +219,9 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
rebuildKeyToPositionMap(keyToPosition, updatedList, dataSource);
data = updatedList;
liveData.postValue(updatedList);
dataStream.next(updatedList);
if (DEBUG) Log.d(TAG, buildItemInsertedLog(key, position, "Published updated data"));
});
}
@@ -226,7 +236,15 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
}
}
private static String buildLog(int aroundIndex, String message) {
return "onDataNeededAroundIndex(" + aroundIndex + ") " + message;
private String buildDataNeededLog(int aroundIndex, String message) {
return "[onDataNeededAroundIndex(" + aroundIndex + "), size: " + loadState.size() + "] " + message;
}
private String buildItemInsertedLog(Key key, int position, String message) {
return "[onDataItemInserted(" + key + ", " + position + "), size: " + loadState.size() + "] " + message;
}
private String buildItemChangedLog(Key key, String message) {
return "[onDataItemInserted(" + key + "), size: " + loadState.size() + "] " + message;
}
}

View File

@@ -0,0 +1,25 @@
package org.signal.paging;
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import java.util.List;
/**
* An implementation of {@link PagedData} that will provide data as a {@link LiveData}.
*/
public class LivePagedData<Key, Data> extends PagedData<Key> {
private final LiveData<List<Data>> data;
LivePagedData(@NonNull LiveData<List<Data>> data, @NonNull PagingController<Key> controller) {
super(controller);
this.data = data;
}
@AnyThread
public @NonNull LiveData<List<Data>> getData() {
return data;
}
}

View File

@@ -0,0 +1,27 @@
package org.signal.paging;
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import java.util.List;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.subjects.Subject;
/**
* An implementation of {@link PagedData} that will provide data as an {@link Observable}.
*/
public class ObservablePagedData<Key, Data> extends PagedData<Key> {
private final Observable<List<Data>> data;
ObservablePagedData(@NonNull Observable<List<Data>> data, @NonNull PagingController<Key> controller) {
super(controller);
this.data = data;
}
@AnyThread
public @NonNull Observable<List<Data>> getData() {
return data;
}
}

View File

@@ -2,39 +2,41 @@ package org.signal.paging;
import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.util.List;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
import io.reactivex.rxjava3.subjects.Subject;
/**
* The primary entry point for creating paged data.
*/
public final class PagedData<Key, Data> {
public class PagedData<Key> {
private final LiveData<List<Data>> data;
private final PagingController<Key> controller;
@AnyThread
public static <Key, Data> PagedData<Key, Data> create(@NonNull PagedDataSource<Key, Data> dataSource, @NonNull PagingConfig config) {
MutableLiveData<List<Data>> liveData = new MutableLiveData<>();
PagingController<Key> controller = new BufferedPagingController<>(dataSource, config, liveData);
return new PagedData<>(liveData, controller);
}
private PagedData(@NonNull LiveData<List<Data>> data, @NonNull PagingController<Key> controller) {
this.data = data;
protected PagedData(PagingController<Key> controller) {
this.controller = controller;
}
@AnyThread
public @NonNull LiveData<List<Data>> getData() {
return data;
public static <Key, Data> LivePagedData<Key, Data> createForLiveData(@NonNull PagedDataSource<Key, Data> dataSource, @NonNull PagingConfig config) {
MutableLiveData<List<Data>> liveData = new MutableLiveData<>();
PagingController<Key> controller = new BufferedPagingController<>(dataSource, config, liveData::postValue);
return new LivePagedData<>(liveData, controller);
}
@AnyThread
public @NonNull PagingController<Key> getController() {
public static <Key, Data> ObservablePagedData<Key, Data> createForObservable(@NonNull PagedDataSource<Key, Data> dataSource, @NonNull PagingConfig config) {
Subject<List<Data>> subject = BehaviorSubject.create();
PagingController<Key> controller = new BufferedPagingController<>(dataSource, config, subject::onNext);
return new ObservablePagedData<>(subject, controller);
}
public PagingController<Key> getController() {
return controller;
}
}