The TLDR is that, in a dense map and with not that may interactive points of interest (POI, hundreds), DistanceFromCharacter() is predictable and fast. ~0.7ms for 1000 POI on my laptop, and it’s what I typically use. FindPartsInRegion3WithWhitelist can beat it on a baseplate with a small region3 (tens of studs on a side), but in practice, cost can explode to many times the cost of the distance method as you increase the size of the region–both absolutely, but particularly in relation to the overall part density of the world. Where the region3 method clearly wins is for very large numbers of POI (tens of thousands) where the O(N) cost of distance checks exceeds available calculation time, and when you only care about things in a small area around the player. In this case, it scales better.
The full answer to this is still fairly straightforward, and the best choice will depend on a couple of factors. First, look at the components of cost for each option:
For DistanceFromCharacter, there is effectively just linear scaling with number of POI. The cost is a straightforward O(N), linear with the number of interactive items. Most of this is Lua loop overhead, since a Lua loop iteration is a lot more expensive than the DistanceFromCharacter call itself. This approach is completely invariant to the distances, distribution of the items, non-POI part-count and part-density of the world. So, in terms of runtime performance, this search has O(N) for it’s worst, average, and best cases. It’s steady, and you know what you’re gonna get. For < 1000 points to test, this method is sub-millisecond.
The cost situation with FindPartsInRegion3WithWhitelist is a lot more complex. Collision tests with the whitelisted parts are AABB checks, which is more expensive than just distance. But, all the parts are in an Octree, so if your region is small relative to the typical spacing between parts in the world (all parts, not just interactive ones), you end up testing very few parts, so pretty negligible. The end result is that your worse-case cost is roughly proportional to the volume of your region times part density of the most part-dense part of world. Best case runtime is super fast, when there are just a few parts in the region to check against the white list. Average case is cost of iterating over the average number of parts likely to be in the specified R3 volume. Worst case is if there is a place on the map that has thousands of parts, including ones on the whitelist, all within a volume that can be enclosed by the region3 size you’ve chosen. If you use a big region3, hundreds of studs on a side, encountering a part-dense area on your map could be cripplingly expensive. The upper bound is a lot harder to estimate than for the simple distance check case.
If you’re doing proximity checks on the server, for when you can’t trust clients, it’s rarely necessary to do the checks for all players, every frame. You can usually amortize this by checking a few players per heartbeat. It’s also worth noting that with the distance check, you might also have other game-specific information that hugely reduces how often you need to do your tests. For example, if you have an open-world exploration map where the player is on foot moving at up to 16 studs per second, and objectives don’t move, then if your distance checks find that the nearest objective is 160 studs away, you don’t even need to run the checks again for nearly 10 seconds, unless the player teleports or resets or does some other form of discontinuous jump. You can cull a lot of checks this way, and even partition objectives by minimum time it would take to reach them. But don’t do any of it until you need to.