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

Feature: Custom Resettable Attribute #98

Merged
merged 12 commits into from
Apr 5, 2024
Merged

Feature: Custom Resettable Attribute #98

merged 12 commits into from
Apr 5, 2024

Conversation

rasberik
Copy link
Collaborator

@rasberik rasberik commented Apr 3, 2024

Pull Request Type

  • Bug fix
  • New feature
  • Refactoring
  • Documentation update
  • Chore

Description

Add Resettable attribute that allows an atom to have a reset callback


if let cache = lookupCache(of: atom, for: key) {
let context = AtomCurrentContext(store: self, coordinator: state.coordinator)
atom.reset(context: context)
Copy link
Collaborator Author

@rasberik rasberik Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ra1028
The most important part for review, as there are multiple potential options.

Since reset itself is a void function without side-effects by itself, but also can have a lot of them depending on what is being called (operations affecting the store & cache). As a result, its possible that it will have serious side effects and multiple downstream updates, as well as possible to have none.

This suggested implementation has following key characteristics (that can be discussed)

  • It executes regardless of override status. This is because code should not care about if atom is overriden or not when it comes to reset, given that it might be expecting key side-effect handling or state change, and override (which is commonly utilized in tests) would break that contract and make environment not clean.

  • It executes before new cache generation and update (not after). Im not convinced myself if this is a proper approach here, and would like to get your input. Reason I chose this way is simply because if reset logic has important data handling that depend on current state of the system, they can be lost if atom is reset and all subscribers are notified. But that as well might be expected otherwise sometimes.. Third option would be not to generate new cache / update at all.. In that case, if atom value is not watching anything, then it effectively disables reset ability to initial state. I sort of considered using _loader.shouldUpdate to check whether there were changes to atom or not when deciding if to make new cache or not, but shouldUpdate always return true except in modified/select atoms.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It executes regardless of override status

It's not necessary to take into account the override status. It's kind of a compromise, but Refreshable also calls the atom's custom refresh regardless of the atom is overridden or not.

It executes before new cache generation and update.

Rather, do not create make a new cache after calling the custom reset. Ressetable should allow atoms to have arbitrary "reset" behavior. If you reset the dependencies of the atom, it re-creates the atom value transitively, if not, then it simply calls arbitrary reset behavior.

Copy link
Collaborator Author

@rasberik rasberik Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's kind of a compromise, but Refreshable also calls the atom's custom refresh regardless of the atom is overridden or not.

        if let override {
            value = override.value(atom)
        }
        else {
            let context = AtomCurrentContext(store: self, coordinator: state.coordinator)
            value = await atom.refresh(context: context)
        }

@ra1028 What am I missing here www

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, that was my slight misunderstanding. More accurately, the custom refresh isn't performed when it's overridden and just returns the overridden value.
This means that custom behavior by attributes is not fully supported when overridden, so reset doesn't really need to care about it for now.

Copy link
Owner

@ra1028 ra1028 Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, come to think of it, refresh may also better follow how reset works that calls the custom reset regardless it's overridden 🤔

README.md Outdated
```swift
struct RandomIntAtom: ValueAtom, Resettable, Hashable {
func value(context: Context) -> Int {
let generator = context.watch(RandomNumberGeneratorAtom())
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not tested, but tried to expand existing example not to introduce completely new context. Used this as reference

}

let store = AtomStore()
let transaction = Transaction(key: AtomKey(transactionAtom)) {}
Copy link
Collaborator Author

@rasberik rasberik Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This confuses me, need some irrelevant atom or test fails.. Transaction concept is difficult to grasp

@rasberik rasberik changed the title Custom Resettable Attribute Feature: Custom Resettable Attribute Apr 3, 2024
README.md Show resolved Hide resolved
Sources/Atoms/Context/AtomContext.swift Show resolved Hide resolved
Comment on lines 115 to 120
/// let context = ...
/// print(context.watch(TextAtom())) // Prints "Text"
/// context[TextAtom()] = "New text"
/// print(context.read(TextAtom())) // Prints "New text"
/// context.reset(TextAtom())
/// print(context.read(TextAtom())) // Prints "Text"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think those example code should be a bit different from the original reset but I have no idea of good example 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard make good example without writing long atom describing code. Updated but its still not very appealing, let me know what you think

Sources/Atoms/Core/StoreContext.swift Show resolved Hide resolved

if let cache = lookupCache(of: atom, for: key) {
let context = AtomCurrentContext(store: self, coordinator: state.coordinator)
atom.reset(context: context)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It executes regardless of override status

It's not necessary to take into account the override status. It's kind of a compromise, but Refreshable also calls the atom's custom refresh regardless of the atom is overridden or not.

It executes before new cache generation and update.

Rather, do not create make a new cache after calling the custom reset. Ressetable should allow atoms to have arbitrary "reset" behavior. If you reset the dependencies of the atom, it re-creates the atom value transitively, if not, then it simply calls arbitrary reset behavior.

@rasberik rasberik requested a review from ra1028 April 4, 2024 13:14
@rasberik rasberik marked this pull request as ready for review April 4, 2024 13:24
Copy link
Owner

@ra1028 ra1028 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ra1028 ra1028 merged commit 657d1cf into ra1028:main Apr 5, 2024
6 checks passed
@rasberik rasberik deleted the feat/custom-resettable branch April 8, 2024 01:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants