Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move from index based to uniqueid based component registry #9

Merged
merged 7 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# shadowlist (ios alpha release)
# shadowlist [alpha release]

ShadowList is a new alternative to FlatList for React Native, created to address common performance issues and enhance the UX when dealing with large lists of data.
It invokes Yoga for precise layout measurements of Shadow Nodes and constructs a Fenwick Tree with layout metrics for efficient offset calculations. By virtualizing children and rendering only items within the visible area, ShadowList ensures optimal performance. It's built on Fabric and works with React Native version 0.74 and newer.
It invokes Yoga for precise layout measurements of Shadow Nodes and constructs a Fenwick Tree with layout metrics for efficient offset calculations. By virtualizing children and rendering only items within the visible area, ShadowList ensures optimal performance. It's built on Fabric and works with React Native version 0.75 and newer.

## Out of box comparison to FlatList
| Feature | ShadowList | FlatList |
Expand Down Expand Up @@ -77,10 +77,6 @@ import {SLContainer} from 'shadowlist';
| `scrollToIndex` | Function | Scrolls the list to the specified index. |
| `scrollToOffset`| Function | Scrolls the list to the specified offset. |

## Contributing

See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.

## License

MIT
31 changes: 13 additions & 18 deletions android/src/main/java/com/shadowlist/SLComponentRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,32 @@ public SLComponentRegistry() {
}

private native long nativeInit();
private native void nativeRegisterComponent(long nativePtr, int componentId);
private native void nativeUnregisterComponent(long nativePtr, int componentId);
private native void nativeMountRange(long nativePtr, int visibleStartIndex, int visibleEndIndex);
private native void nativeMount(long nativePtr, int[] indices);
private native void nativeUnmount(long nativePtr, int[] indices);
private native void nativeRegisterComponent(long nativePtr, String uniqueId);
private native void nativeUnregisterComponent(long nativePtr, String uniqueId);
private native void nativeMount(long nativePtr, String[] uniqueIds);
private native void nativeUnmount(long nativePtr, String[] uniqueIds);
private native void nativeMountObserver(long nativePtr, SLObserver observer);
private native void nativeUnmountObserver(long nativePtr, SLObserver observer);
private native void nativeDestroy(long nativePtr);

public interface SLObserver {
void onVisibilityChanged(int id, boolean isVisible);
void onVisibilityChanged(String uniqueId, boolean isVisible);
}

public void registerComponent(int componentId) {
nativeRegisterComponent(mNativePtr, componentId);
public void registerComponent(String uniqueId) {
nativeRegisterComponent(mNativePtr, uniqueId);
}

public void unregisterComponent(int componentId) {
nativeUnregisterComponent(mNativePtr, componentId);
public void unregisterComponent(String uniqueId) {
nativeUnregisterComponent(mNativePtr, uniqueId);
}

public void mountRange(int visibleStartIndex, int visibleEndIndex) {
nativeMountRange(mNativePtr, visibleStartIndex, visibleEndIndex);
public void mount(String[] uniqueIds) {
nativeMount(mNativePtr, uniqueIds);
}

public void mount(int[] indices) {
nativeMount(mNativePtr, indices);
}

public void unmount(int[] indices) {
nativeUnmount(mNativePtr, indices);
public void unmount(String[] uniqueIds) {
nativeUnmount(mNativePtr, uniqueIds);
}

public void mountObserver(SLObserver observer) {
Expand Down
2 changes: 1 addition & 1 deletion android/src/main/java/com/shadowlist/SLContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) {

@Override
public void addView(View child, int index) {
mContainerChildrenManager.mountChildComponentView(child, index);
mContainerChildrenManager.mountChildComponentView(child, ((SLElement)child).getUniqueId());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,32 @@

import android.view.View;
import com.facebook.react.views.view.ReactViewGroup;
import java.util.HashMap;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

public class SLContainerChildrenManager {
private ReactViewGroup mScrollContent;
private SLComponentRegistry mChildrenRegistry;
private Map<Integer, View> mChildrenPool;
private Map<String, View> mChildrenPool;

public SLContainerChildrenManager(ReactViewGroup contentView) {
this.mScrollContent = contentView;
this.mChildrenRegistry = new SLComponentRegistry();

mChildrenRegistry.mountObserver((index, isVisible) -> {
mChildrenRegistry.mountObserver((uniqueId, isVisible) -> {
try {
mountObserver(index, isVisible);
mountObserver(uniqueId, isVisible);
} catch (IndexOutOfBoundsException e) {}
});

this.mChildrenPool = new HashMap<>();
}

private void mountObserver(int index, boolean isVisible) {
View child = mChildrenPool.get(index);
private void mountObserver(String uniqueId, boolean isVisible) {
View child = mChildrenPool.get(uniqueId);

if (isVisible) {
mScrollContent.addView(child);
Expand All @@ -33,23 +36,35 @@ private void mountObserver(int index, boolean isVisible) {
}
}

public void mountChildComponentView(View childComponentView, int index) {
mChildrenPool.put(index, childComponentView);
mChildrenRegistry.registerComponent(index);
public void mountChildComponentView(View childComponentView, String uniqueId) {
mChildrenPool.put(uniqueId, childComponentView);
mChildrenRegistry.registerComponent(uniqueId);
}

public void unmountChildComponentView(View childComponentView, int index) {
mChildrenRegistry.unregisterComponent(index);
mChildrenPool.remove(index);
public void unmountChildComponentView(View childComponentView, String uniqueId) {
mChildrenRegistry.unregisterComponent(uniqueId);
mChildrenPool.remove(uniqueId);
}

public void mount(int visibleStartIndex, int visibleEndIndex) {
mChildrenRegistry.mountRange(visibleStartIndex, visibleEndIndex);
List<String> mounted = new ArrayList<>();

for (Map.Entry<String, View> entry : mChildrenPool.entrySet()) {
SLElement childComponentView = (SLElement) entry.getValue();

if (childComponentView.getIndex() >= visibleStartIndex && childComponentView.getIndex() <= visibleEndIndex) {
mounted.add(childComponentView.getUniqueId());
}
}

String[] mountedArray = mounted.toArray(new String[0]);
mChildrenRegistry.mount(mountedArray);
}


public void destroy() {
for (Integer index : mChildrenPool.keySet()) {
unmountChildComponentView(mChildrenPool.get(index), index);
for (String uniqueId : mChildrenPool.keySet()) {
unmountChildComponentView(mChildrenPool.get(uniqueId), uniqueId);
}
mChildrenRegistry.destroy();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class SLContainerPackage implements ReactPackage {
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> viewManagers = new ArrayList<>();
viewManagers.add(new SLContainerManager());
viewManagers.add(new SLElementManager());
return viewManagers;
}

Expand Down
50 changes: 50 additions & 0 deletions android/src/main/java/com/shadowlist/SLElement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.shadowlist;

import android.content.Context;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.view.View;
import android.graphics.Canvas;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;

import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.mapbuffer.MapBuffer;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.react.views.view.ReactViewGroup;

public class SLElement extends ReactViewGroup {
private int mIndex;
private String mUniqueId;

public SLElement(Context context) {
super(context);
init(context);
}

public SLElement(Context context, AttributeSet attrs) {
super(context);
init(context);
}

private void init(Context context) {
}

public int getIndex() {
return this.mIndex;
}

public String getUniqueId() {
return this.mUniqueId;
}

public void setIndex(int index) {
this.mIndex = index;
}

public void setUniqueId(String uniqueId) {
this.mUniqueId = uniqueId;
}
}
61 changes: 61 additions & 0 deletions android/src/main/java/com/shadowlist/SLElementManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.shadowlist;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.StateWrapper;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewManagerDelegate;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.common.mapbuffer.MapBuffer;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.viewmanagers.SLElementManagerInterface;
import com.facebook.react.viewmanagers.SLElementManagerDelegate;

import java.util.Map;

@ReactModule(name = SLElementManager.NAME)
public class SLElementManager extends ViewGroupManager<SLElement>
implements SLElementManagerInterface<SLElement> {

private final ViewManagerDelegate<SLElement> mDelegate;

public SLElementManager() {
mDelegate = new SLElementManagerDelegate(this);
}

@Override
public ViewManagerDelegate<SLElement> getDelegate() {
return mDelegate;
}

@Override
public String getName() {
return NAME;
}

@Override
public SLElement createViewInstance(ThemedReactContext context) {
SLElement view = new SLElement(context);
return view;
}

public static final String NAME = "SLElement";

@Override
public void setIndex(SLElement view, @Nullable int value) {
view.setIndex(value);
}

@Override
public void setUniqueId(SLElement view, @Nullable String value) {
view.setUniqueId(value);
}
}
23 changes: 11 additions & 12 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE ON)

set(LIB_LITERAL SLContainerSpec)
set(LIB_LITERAL_FENWICK fenwick)
set(LIB_LITERAL_REGISTRY registry)
set(LIB_TARGET_NAME react_codegen_${LIB_LITERAL})

set(LIB_TARGET_NAME react_codegen_SLContainerSpec)
set(LIB_ANDROID_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../android)
set(LIB_COMMON_DIR ${CMAKE_CURRENT_SOURCE_DIR})

Expand All @@ -18,11 +14,13 @@ add_compile_options(
-Wno-gnu-zero-variadic-macro-arguments
)

file(GLOB LIB_IMPORT_SRCS CONFIGURE_DEPENDS SLContainerSpec.cpp)
file(GLOB LIB_CUSTOM_SRCS CONFIGURE_DEPENDS
${LIB_COMMON_DIR}/react/renderer/components/${LIB_LITERAL}/*.cpp
${LIB_COMMON_DIR}/${LIB_LITERAL_FENWICK}/*.cpp
${LIB_COMMON_DIR}/${LIB_LITERAL_REGISTRY}/*.cpp
SLContainerSpec.cpp
SLElementSpec.cpp
${LIB_COMMON_DIR}/react/renderer/components/SLContainerSpec/*.cpp
${LIB_COMMON_DIR}/react/renderer/components/SLElementSpec/*.cpp
${LIB_COMMON_DIR}/fenwick/*.cpp
${LIB_COMMON_DIR}/registry/*.cpp
)

add_library(
Expand All @@ -39,9 +37,10 @@ target_include_directories(
${LIB_COMMON_DIR}
${LIB_IMPORT_SRCS}
${LIB_CUSTOM_SRCS}
${LIB_COMMON_DIR}/react/renderer/components/${LIB_LITERAL}
${LIB_COMMON_DIR}/${LIB_LITERAL_FENWICK}
${LIB_COMMON_DIR}/${LIB_LITERAL_REGISTRY}
${LIB_COMMON_DIR}/react/renderer/components/SLContainerSpec
${LIB_COMMON_DIR}/react/renderer/components/SLElementSpec
${LIB_COMMON_DIR}/fenwick
${LIB_COMMON_DIR}/registry
)

target_link_libraries(
Expand Down
11 changes: 11 additions & 0 deletions cpp/SLElementSpec.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifdef ANDROID
#include "SLElementSpec.h"

namespace facebook::react {

std::shared_ptr<TurboModule> SLElementSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
return nullptr;
}

}
#endif
15 changes: 15 additions & 0 deletions cpp/SLElementSpec.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#ifdef ANDROID
#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
#include "SLElementComponentDescriptor.h"

namespace facebook::react {

JSI_EXPORT
std::shared_ptr<TurboModule> SLElementSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params);

}
#endif
Loading
Loading