Git Product home page Git Product logo

ore-ui's Introduction

Ore UI meta-repo

Open-source building blocks from Mojang Studios to construct video game user interfaces (UI's) using web standards.

What is this?

This repository contains some infrastructure bits used internally, but that can also be shared across other game studios and general-purpose web applications.

Some of the projects using this tech are:

  • Minecraft Bedrock Edition
  • Minecraft Legends

Ore UI is based on:

Packages

  • @react-facet: Observable-based state management for performant game UIs built in React
  • @mojang/react-gamepad: Declarative Gamepad focus navigation (coming soon)
  • More to come!

Documentation

We currently only have one package that is open-source, and its documentation is available at https://react-facet.mojang.com/.

The source of the documentation (for contributions) is available at the documentation branch.

Maintainers

The repository is maintained by JavaScript developers at Mojang Studios.


Paulo Ragonha

Fernando Vía Canel

Marlon Huber-Smith

Anna Päärni

Danila Dergachev

Omar ElGaml

James Nicholls

Logo

The React Facet and Ore UI logos are designed by Nekofresa.

ore-ui's People

Contributors

adamramberg avatar bahamoe avatar creativecreature avatar dderg avatar ja-ni avatar jacobbergdahl avatar marlonicus avatar pedr0fontoura avatar pillimoj avatar pirelenito avatar seriema avatar shenato avatar tommy-malone-zos avatar vb avatar volgar avatar xaviervia avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ore-ui's Issues

RFC: Dynamic path for shared facets

Motivation

Shared facets are defined statically at a global level using the sharedFacet function as a combination of a name and a type. As an example from our documentation:

export const userFacet = sharedFacet<UserFacet>('data.user', {
  username: 'Alex'
  signOut() {},
})

We want to take the existing concept of a name and expand it to be more of a path to look into a big global state object within the game engine.

Thinking about the previous example, the name data.user could be interpreted the as the path to the contents of the user in the object below:

{
  data: {
    user: {}
  }
}

This is really interesting when we consider that we could build a path dinamically in a React component to point to an specific instance of an object. Let's say we have a list of creatures in a game:

{
  entity: {
    hostile: {
      '123': { name: 'creeper' },
      '456': { name: 'piglin' }
    }
  }
}

And we want to subscribe to changes only to the creeper entity. With the current static definition of shared facets this could only be achieved via a dynamicSelector, but then every selector would be notified when any entity changes. So we propose that we remove sharedFacet as a function and instead have our API for subscribing to a shared facet to be completely handled in a hook by taking both the path and the type:

const entityFacet = useSharedFacet<Entity>(['entity', 'hostile', '123'])

Type safety

cc @xaviervia can you add some notes about it?

Global definitions

We could still have a "global definition" of a facet, but this would be instead achieved via a hook:

--- export const userFacet = sharedFacet<UserFacet>('data.user')
+++ export const useUserFacet = useSharedFacet<UserFacet>(['data', 'user'])

Deprecations

This change will remove/deprecate the APIs for:

  • sharedFacet
  • sharedSelector
  • sharedDynamicSelector

In favor of using hooks and useFacetMap or useFacetMemo to achieve the same functionality.

It is important to assess if there are any risks on removing these APIs. And although we would be removing them from the open-source packages, they could still be kept implemented internally by consumers of our packages (if we need to provide any backwards compatibility).

Implementation considerations

When requesting a shared facet to the engine we need to make sure we only make one request for a given "path", even if multiple places are subscribing to it. This is currently achieved by having a single instance for each shared facet via memoisation in the sharedFacet definition.

This behavior would need to be re-implemented as part of the useSharedFacet hook.

Calls to callbacks created via useFacetCallback can get outdated values

In a scenario where a function that is listening on a facet is calling another function created with useFacetCallback, the value of the same facet inside the second callback might be outdated.

For example:

const facet = createFacet({ initialValue: 'old value' })

function Comp() {
  // Since this is run on useLayoutEffect, it will
  // add the 
  const callback = useFacetCallback((value) => value, [], [facet])

  // Since this is run on render, it will add the
  // `.observe` subscription first
  useMemo(() => {
    facet.observe((value) => {
      console.log('here be new value: ', value)
      console.log('...and here is still old value: ', callback())
    })
  })
  
  return null
}

facet.set('new value')

RFC: Change strategy on "default equality checks"

There is room to improve a bit further our strategy regarding equality checks. Right now we have a "default equality check" that is applied everywhere, but this default implementation adds a runtime type check on every useFacetMap and useFacetMemo we add.

The goal here is to remove the overhead and align the behavior of equality checks to what is expected from "vanilla React". React has no memoization by default, and we can probably lean on the same premise.

useFacetMap and useFacetMemo

Change to have NO equality checks set by default, and if needed it should be added explicitly by the user. This will make our map implementation much closer to React's component (React re-renders a component if called with the same values).

useFacetState

Here, we have two options:

  • We could go "immutable" and behave exactly as React by default. Meaning that if you set the state with the same reference, it doesn't trigger an update. Given this is a local state, it might be safe to take this approach, mutability is only an issue with "remote facets".
  • Another option would be to remove completely the equality checks, and any "set" would trigger an update (even with the same reference). This would diverge from React, but it might be more friendly to use in conjunction with "remote facets".

With either approach we would still support being explicit about which equality check to use (as it is today).

useFacetEffect

The proposal would be to maintain the behavior we have today, but as an "effect" of the previous changes it would mean for users our useFacetEffect would behave differently from useEffect.

Given the equality checks would be removed from the maps, effects would always be triggered, even for updates with the same reference.

We could extend the API to support equality checks, but given we can have multiple facets as a dependency, it would become quite verbose to setup any checks.

So, to prevent effects being fired, users would need to add equality checks on outer layers (ex: useFacetMap), if needed.

dom-fiber and dom-components

To support this change, our custom renderer would need to start performing equality checks on each prop to know for sure if the value of a facet have changed. The benefit here is that we would know for sure the type of the Facet and can do strict equality checks.

Feature request: return a ref from useFacetState

Example:

const [valueFacet, setValue, valueRef] = useFacetState(0)

This would be equivalent to doing

const [valueFacet, setValue] = useFacetState(0)
const valueRef = useFacetRef(valueFacet)

But then there would be no need for useFacetRef do use a useFacetEffect, instead updating the ref could happen in the setter. This would help with performance, and make the type of the ref cleaner (since it can't be NO_VALUE in this case)

React 18 support

Is there anything that stops from updating to the last version of react? Current one is 16 which is pretty old

Feature request: Introduce DataSharedFacetValue and MethodSharedFacetValue subtypes

Moving forward, we don't want to mix and match methods and data in shared facets. We want to think of data facets as pure data, much like Redux state, and method facet as flat lists of available API calls from the remote.

Additionally, MethodSharedFacetValues could automatically do a .bind on the methods it includes, to avoid issues with selecting individual methods from the facet and losing the this reference.

Non-blocking DeferredMountWithCallback

As of #34, when DeferredMountWithCallback is used, all deferred rendering is paused until that callback is invoked.

To avoid blocking all rendering, we might want to place the callback in a second queue for notifying when all rendering is complete, but avoid blocking the start of render of the next component. This should be optional, so that we can also block when waiting for render to be completed before starting the next.

This change does not break API.

We should introduce a new property for signalling when we explicitly want to block further rendering, it could look something like this:

<DeferredMountWithCallback isBlocking>
  <ComponentThatShouldBeWaitedFor />
</DeferredMountWithCallback>

Introduce With component

Example:

type UserDataProps = {
  name: FacetProp<string>
  middlename?: FacetProp<string | undefined>
}

const UserData = ({ name, middlename }: UserDataProps) => {
  const nameFacet = useFacetWrap(name)
  const middlenameFacet = useFacetWrap(middlename)
  
  return (
    <div>
      <p>Name: <fast-text text={nameFacet} /></p>
      <With data={middlenameFacet}>
        {(middlename) => <p>Middlename: <fast-text text={middlename} /></p>}
      </With>
    </div>
  )
}

Why?

The current component that supports a similar use case, Mount, has the defect that TypeScript is not able to tell when the data used inside it is defined. It cannot use the when clause to refine the type. In particular, the following code will lead to an annoying type error (since you as a developer know the code is correct, but TypeScript doesn't)

type UserDataProps = {
  name: FacetProp<string>
  middlename?: FacetProp<string | undefined>
}

const UserData = ({ name, middlename }: UserDataProps) => {
  const nameFacet = useFacetWrap(name)
  const middlenameFacet = useFacetWrap(middlename)
  
  return (
    <div>
      <p>Name: <fast-text text={nameFacet} /></p>
      <Mount data={useFacetMap((middlename) => middlename != null, [],[middlenameFacet])}>
        <p>Middlename: <fast-text text={middlenameFacet} /></p> 
        {/* Since TypeScript cannot know that `middlenameFacet` now holds a `string` for sure and still thinks that
            it could be `string | undefined`, it will complain. The only way to fix this is to extract a new component
            or with a type assertion. Neither is good */}
      </With>
    </div>
  )
}

TODO

  • Implement
  • Document

How should one use Map with a SharedFacet?

e.g., in this example:

import { Map, useFacetState } from "@react-facet/core";
import { sharedFacet, sharedDynamicSelector, useSharedFacet } from "@react-facet/shared-facet";

interface Member {
    name: string
    rank: number
    health: number
}

const membersArray = [
    {
        name: "Zeus",
        rank: 3,
        health: 90
    },
    {
        name: "Demeter",
        rank: 2,
        health: 100
    },
    {
        name: "Poseidon",
        rank: 1,
        health: 85
    }
];

const membersFacet = sharedFacet<Member[]>("data.team", membersArray);

const memberNameSelector = sharedDynamicSelector((index: number) => [
    (member) => member[index].name,
    [membersFacet]
]);

const memberRankSelector = sharedDynamicSelector((index: number) => [
    (member) => member[index].rank,
    [membersFacet]
]);

const memberHealthSelector = sharedDynamicSelector((index: number) => [
    (member) => member[index].health,
    [membersFacet]
]);

const Member = ({ index }: { index: number }) => {
    return (
        <span>
            <b>Name: </b><fast-text text={useSharedFacet(memberNameSelector(index))}/>
            <br/>
            <b>Rank: </b><fast-text text={useSharedFacet(memberRankSelector(index))}/>
            <br/>
            <b>HP: </b><fast-text text={useSharedFacet(memberHealthSelector(index))}/>
            <br/>
            <hr/>
        </span>
    );
};

export const Team = () => {
    // works (as expected)
    // const [membersFacetState] = useFacetState<Member[]>(membersArray);

    // does not
    const membersFacetState = useFacetState(membersFacet);

    return (
        <Map array={membersFacetState}>
            {(_, index) => <Member index={index} />}
        </Map>
    );
};

the type error returned on Map's "array" prop:

TS2322: Type
[Facet<SharedFacet<Member[]>>, Setter<SharedFacet<Member[]>>]
is not assignable to type Facet<unknown[]>
Map.d.ts(4, 5): The expected type comes from property array which is declared here on type IntrinsicAttributes & MapProps<unknown>

I've tried digging through the docs, attempting to map from a SharedFacet to a Facet (couldn't seem to get that working), so figured I'd ask here. Thanks!

useFacetEffect will never fire if there are no facet dependencies

Right now, if we run:

useFacetEffect(() => { console.log('I will never fire' }, [], [])

The effect will never fire, because useFacetEffect expects for all facets to resolve to a value before firing the effect. In this case, there are no facets to resolve in the second dependency array, so it will just never fire at all.

In a way this scenario doesn't make sense (we should use useEffect instead) but since there's no way to enforce that empty arrays are not allowed, we should fail gracefully. Instead of never firing, useFacetEffect with an empty facet array should just behave in exactly the same way as a regular useEffect. This, we think, would be the expected behavior.

This issue applies also to a similar scenario in useFacetCallback. We should fix that one as well in the same way.

RFC: meta information on which facet triggered re-run of hook

The Problem

In some scenarios you need to keep track of more than one facet in one hook like useFacetEffect(), however the downside is you never really know which one of them triggered the current run of the effect, one option is is to split the effect, but sometimes that's not feasible depending on if the logic actually depends on more than one facet at the same time and they're not unrelated.

Proposed API change

The hook could accept a callback function overload whereby if the callback function has one more parameter than the facets passed to the hook it would keep track of the values and give you a perhaps meta object (name pending) with simple booleans for the facet's position in the dependency array which indicate which facet changed this run.

This should be an opt-in approach (by expecting an extra parameter in a X facet dependency hook), so we don't have to pay that cost in normal scenarios which are the abundant use-case for those hooks.

Example code below of how it can be used:

	useFacetEffect(
		(firstFacetValue, secondFacetValue, meta) => {
		        if(meta.changed[0]) {
		           // Do something if firstFacetValue is the facet that changed triggering this effect
		        }
	
	                if(meta.changed[1]) {
		           // Do something if secondFacetValue is the facet that changed triggering this effect
		        }
		},
		[],
		[firstFacet, secondFacet],
	)

Hello

Can I help with coding make me member please I love coding

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.